From e344a3b5817f00c38fae2e183d84d43575667342 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 19 Sep 2025 11:54:49 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .codeclimate.yml | 4 + .editorconfig | 7 + .github/dependabot.yml | 7 + .github/workflows/ci.yml | 147 + .github/workflows/codeql-analysis.yml | 47 + .github/workflows/needinfo-remove.yml | 25 + .github/workflows/needinfo-stale.yml | 21 + .github/workflows/publish.yaml | 53 + .gitignore | 12 + CHANGELOG.md | 55 + CODE_OF_CONDUCT.md | 15 + CONTRIBUTING.md | 46 + LICENSE | 373 + README.md | 120 + docs/index.html | 7 + eslint.config.js | 389 + jsdoc-prepare.json | 10 + jsdoc.json | 20 + karma.conf.cjs | 48 + lib/ical/binary.js | 173 + lib/ical/component.js | 621 + lib/ical/component_parser.js | 157 + lib/ical/design.js | 1026 ++ lib/ical/duration.js | 354 + lib/ical/event.js | 569 + lib/ical/helpers.js | 317 + lib/ical/module.js | 73 + lib/ical/parse.js | 547 + lib/ical/period.js | 245 + lib/ical/property.js | 439 + lib/ical/recur.js | 576 + lib/ical/recur_expansion.js | 475 + lib/ical/recur_iterator.js | 1441 ++ lib/ical/stringify.js | 302 + lib/ical/time.js | 1334 ++ lib/ical/timezone.js | 526 + lib/ical/timezone_service.js | 129 + lib/ical/types.js | 93 + lib/ical/utc_offset.js | 187 + lib/ical/vcard_time.js | 183 + package-lock.json | 11026 ++++++++++++++++ package.json | 104 + rollup.config.js | 67 + samples/blank_description.ics | 51 + samples/blank_line_end.ics | 4 + samples/blank_line_mid.ics | 4 + samples/daily_recur.ics | 52 + samples/day_long_recur_yearly.ics | 52 + samples/duration_instead_of_dtend.ics | 39 + samples/forced_types.ics | 50 + samples/google_birthday.ics | 90 + samples/minimal.ics | 39 + samples/multiple_rrules.ics | 45 + samples/only_dtstart_date.ics | 38 + samples/only_dtstart_time.ics | 38 + samples/parserv2.ics | 45 + samples/recur_instances.ics | 98 + samples/recur_instances_finite.ics | 50 + samples/timezone_from_file.ics | 18 + samples/timezones/America/Atikokan.ics | 14 + samples/timezones/America/Denver.ics | 41 + samples/timezones/America/Los_Angeles.ics | 22 + samples/timezones/America/New_York.ics | 22 + samples/timezones/Makebelieve/RDATE_test.ics | 26 + .../timezones/Makebelieve/RDATE_utc_test.ics | 26 + .../Makebelieve/RRULE_UNTIL_test.ics | 22 + samples/utc_negative_zero.ics | 27 + test/acceptance/blank_description_test.js | 12 + test/acceptance/daily_recuring_test.js | 31 + test/acceptance/forced_types_test.js | 20 + test/acceptance/google_birthday_test.js | 50 + test/acceptance/utc_negative_zero_test.js | 28 + test/binary_test.js | 23 + test/component_parser_test.js | 124 + test/component_test.js | 627 + test/design_test.js | 967 ++ test/duration_test.js | 177 + test/event_test.js | 1026 ++ test/failure_test.js | 67 + test/helper_test.js | 160 + test/parse_test.js | 317 + test/parser/base64.ics | 5 + test/parser/base64.json | 16 + test/parser/boolean.ics | 5 + test/parser/boolean.json | 23 + test/parser/component.ics | 12 + test/parser/component.json | 21 + test/parser/dates.ics | 7 + test/parser/dates.json | 35 + test/parser/escape_semicolon.json | 26 + test/parser/escape_semicolon.vcf | 23 + test/parser/float.ics | 5 + test/parser/float.json | 17 + test/parser/grouped.ics | 3 + test/parser/grouped.json | 12 + test/parser/integer.ics | 5 + test/parser/integer.json | 17 + test/parser/multiple_root_components.ics | 6 + test/parser/multiple_root_components.json | 10 + test/parser/multivalue.ics | 7 + test/parser/multivalue.json | 40 + test/parser/newline_junk.ics | 6 + test/parser/newline_junk.json | 1 + test/parser/period.ics | 5 + test/parser/period.json | 12 + test/parser/property_params.ics | 20 + test/parser/property_params.json | 141 + test/parser/quoted_params.ics | 4 + test/parser/quoted_params.json | 14 + test/parser/recur.ics | 7 + test/parser/recur.json | 35 + test/parser/rfc.ics | 11 + test/parser/rfc.json | 18 + test/parser/single_empty_vcalendar.ics | 2 + test/parser/single_empty_vcalendar.json | 1 + test/parser/time.ics | 5 + test/parser/time.json | 18 + test/parser/tzid_with_gmt.ics | 3 + test/parser/tzid_with_gmt.json | 11 + test/parser/unfold_properties.ics | 5 + test/parser/unfold_properties.json | 16 + test/parser/utc_offset.ics | 6 + test/parser/utc_offset.json | 7 + test/parser/values.ics | 3 + test/parser/values.json | 11 + test/parser/vcard.json | 38 + test/parser/vcard.vcf | 34 + test/parser/vcard3.json | 47 + test/parser/vcard3.vcf | 54 + test/parser/vcard_author.json | 58 + test/parser/vcard_author.vcf | 21 + test/parser/vcard_grouped.json | 9 + test/parser/vcard_grouped.vcf | 5 + test/performance/iterator_test.js | 28 + test/performance/parse_test.js | 17 + test/performance/rrule_test.js | 58 + test/performance/time_test.js | 84 + test/period_test.js | 315 + test/property_test.js | 555 + test/recur_expansion_test.js | 332 + test/recur_iterator_test.js | 1266 ++ test/recur_test.js | 701 + test/stringify_test.js | 230 + test/support/helper.js | 216 + test/support/perfReporter.cjs | 68 + test/time_test.js | 1816 +++ test/timezone_service_test.js | 116 + test/timezone_test.js | 323 + test/utc_offset_test.js | 116 + test/vcard_time_test.js | 69 + tools/ICALTester/Makefile | 32 + tools/ICALTester/README.md | 59 + tools/ICALTester/compare.js | 105 + tools/ICALTester/lib/ICALTester.js | 173 + tools/ICALTester/package-lock.json | 1081 ++ tools/ICALTester/package.json | 19 + tools/ICALTester/rules.json | 16 + tools/ICALTester/support/.gitignore | 2 + tools/ICALTester/support/libical-recur.c | 35 + tools/benchmark/.gitignore | 4 + tools/jsdoc-collect-types.cjs | 29 + tools/jsdoc-ical.cjs | 78 + tools/recur-tester.html | 140 + tools/scriptutils.js | 161 + tools/validator.html | 117 + tsconfig.json | 6 + 166 files changed, 36202 insertions(+) create mode 100644 .codeclimate.yml create mode 100644 .editorconfig create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/codeql-analysis.yml create mode 100644 .github/workflows/needinfo-remove.yml create mode 100644 .github/workflows/needinfo-stale.yml create mode 100644 .github/workflows/publish.yaml create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 docs/index.html create mode 100644 eslint.config.js create mode 100644 jsdoc-prepare.json create mode 100644 jsdoc.json create mode 100644 karma.conf.cjs create mode 100644 lib/ical/binary.js create mode 100644 lib/ical/component.js create mode 100644 lib/ical/component_parser.js create mode 100644 lib/ical/design.js create mode 100644 lib/ical/duration.js create mode 100644 lib/ical/event.js create mode 100644 lib/ical/helpers.js create mode 100644 lib/ical/module.js create mode 100644 lib/ical/parse.js create mode 100644 lib/ical/period.js create mode 100644 lib/ical/property.js create mode 100644 lib/ical/recur.js create mode 100644 lib/ical/recur_expansion.js create mode 100644 lib/ical/recur_iterator.js create mode 100644 lib/ical/stringify.js create mode 100644 lib/ical/time.js create mode 100644 lib/ical/timezone.js create mode 100644 lib/ical/timezone_service.js create mode 100644 lib/ical/types.js create mode 100644 lib/ical/utc_offset.js create mode 100644 lib/ical/vcard_time.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 rollup.config.js create mode 100644 samples/blank_description.ics create mode 100644 samples/blank_line_end.ics create mode 100644 samples/blank_line_mid.ics create mode 100644 samples/daily_recur.ics create mode 100644 samples/day_long_recur_yearly.ics create mode 100644 samples/duration_instead_of_dtend.ics create mode 100644 samples/forced_types.ics create mode 100644 samples/google_birthday.ics create mode 100644 samples/minimal.ics create mode 100644 samples/multiple_rrules.ics create mode 100644 samples/only_dtstart_date.ics create mode 100644 samples/only_dtstart_time.ics create mode 100644 samples/parserv2.ics create mode 100644 samples/recur_instances.ics create mode 100644 samples/recur_instances_finite.ics create mode 100644 samples/timezone_from_file.ics create mode 100644 samples/timezones/America/Atikokan.ics create mode 100644 samples/timezones/America/Denver.ics create mode 100644 samples/timezones/America/Los_Angeles.ics create mode 100644 samples/timezones/America/New_York.ics create mode 100644 samples/timezones/Makebelieve/RDATE_test.ics create mode 100644 samples/timezones/Makebelieve/RDATE_utc_test.ics create mode 100644 samples/timezones/Makebelieve/RRULE_UNTIL_test.ics create mode 100644 samples/utc_negative_zero.ics create mode 100644 test/acceptance/blank_description_test.js create mode 100644 test/acceptance/daily_recuring_test.js create mode 100644 test/acceptance/forced_types_test.js create mode 100644 test/acceptance/google_birthday_test.js create mode 100644 test/acceptance/utc_negative_zero_test.js create mode 100644 test/binary_test.js create mode 100644 test/component_parser_test.js create mode 100644 test/component_test.js create mode 100644 test/design_test.js create mode 100644 test/duration_test.js create mode 100644 test/event_test.js create mode 100644 test/failure_test.js create mode 100644 test/helper_test.js create mode 100644 test/parse_test.js create mode 100644 test/parser/base64.ics create mode 100644 test/parser/base64.json create mode 100644 test/parser/boolean.ics create mode 100644 test/parser/boolean.json create mode 100644 test/parser/component.ics create mode 100644 test/parser/component.json create mode 100644 test/parser/dates.ics create mode 100644 test/parser/dates.json create mode 100644 test/parser/escape_semicolon.json create mode 100644 test/parser/escape_semicolon.vcf create mode 100644 test/parser/float.ics create mode 100644 test/parser/float.json create mode 100644 test/parser/grouped.ics create mode 100644 test/parser/grouped.json create mode 100644 test/parser/integer.ics create mode 100644 test/parser/integer.json create mode 100644 test/parser/multiple_root_components.ics create mode 100644 test/parser/multiple_root_components.json create mode 100644 test/parser/multivalue.ics create mode 100644 test/parser/multivalue.json create mode 100644 test/parser/newline_junk.ics create mode 100644 test/parser/newline_junk.json create mode 100644 test/parser/period.ics create mode 100644 test/parser/period.json create mode 100644 test/parser/property_params.ics create mode 100644 test/parser/property_params.json create mode 100644 test/parser/quoted_params.ics create mode 100644 test/parser/quoted_params.json create mode 100644 test/parser/recur.ics create mode 100644 test/parser/recur.json create mode 100644 test/parser/rfc.ics create mode 100644 test/parser/rfc.json create mode 100644 test/parser/single_empty_vcalendar.ics create mode 100644 test/parser/single_empty_vcalendar.json create mode 100644 test/parser/time.ics create mode 100644 test/parser/time.json create mode 100644 test/parser/tzid_with_gmt.ics create mode 100644 test/parser/tzid_with_gmt.json create mode 100644 test/parser/unfold_properties.ics create mode 100644 test/parser/unfold_properties.json create mode 100644 test/parser/utc_offset.ics create mode 100644 test/parser/utc_offset.json create mode 100644 test/parser/values.ics create mode 100644 test/parser/values.json create mode 100644 test/parser/vcard.json create mode 100644 test/parser/vcard.vcf create mode 100644 test/parser/vcard3.json create mode 100644 test/parser/vcard3.vcf create mode 100644 test/parser/vcard_author.json create mode 100644 test/parser/vcard_author.vcf create mode 100644 test/parser/vcard_grouped.json create mode 100644 test/parser/vcard_grouped.vcf create mode 100644 test/performance/iterator_test.js create mode 100644 test/performance/parse_test.js create mode 100644 test/performance/rrule_test.js create mode 100644 test/performance/time_test.js create mode 100644 test/period_test.js create mode 100644 test/property_test.js create mode 100644 test/recur_expansion_test.js create mode 100644 test/recur_iterator_test.js create mode 100644 test/recur_test.js create mode 100644 test/stringify_test.js create mode 100644 test/support/helper.js create mode 100644 test/support/perfReporter.cjs create mode 100644 test/time_test.js create mode 100644 test/timezone_service_test.js create mode 100644 test/timezone_test.js create mode 100644 test/utc_offset_test.js create mode 100644 test/vcard_time_test.js create mode 100644 tools/ICALTester/Makefile create mode 100644 tools/ICALTester/README.md create mode 100644 tools/ICALTester/compare.js create mode 100644 tools/ICALTester/lib/ICALTester.js create mode 100644 tools/ICALTester/package-lock.json create mode 100644 tools/ICALTester/package.json create mode 100644 tools/ICALTester/rules.json create mode 100644 tools/ICALTester/support/.gitignore create mode 100644 tools/ICALTester/support/libical-recur.c create mode 100644 tools/benchmark/.gitignore create mode 100644 tools/jsdoc-collect-types.cjs create mode 100644 tools/jsdoc-ical.cjs create mode 100644 tools/recur-tester.html create mode 100644 tools/scriptutils.js create mode 100644 tools/validator.html create mode 100644 tsconfig.json diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 0000000..27c5de5 --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,4 @@ +languages: + JavaScript: true +exclude_paths: + - "build/*" diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..6c2eeb8 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +indent_size = 2 +indent_style = space diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..91ba748 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "daily" + open-pull-requests-limit: 10 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b837d7f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,147 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +--- +name: "Checkin" +on: + push: + branches: + - main + pull_request: + +jobs: + build-and-test: + name: "Build & Test" + runs-on: ubuntu-latest + strategy: + matrix: + node: + - lts/* + - node + steps: + - name: "Checkout" + uses: actions/checkout@v4 + + - name: "Setup node" + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + cache: npm + + - name: "npm ci" + run: npm ci + + - name: "Build" + run: npm run build + + - name: "Linters" + run: npm run lint + + - name: "Node Unit Tests" + run: npm run test-unit + + - name: "Node Acceptance Tests" + run: npm run test-acceptance + + # Disabled for the moment, I can't get sauce labs to work + # - name: "Browser Unit Tests" + # env: + # SAUCE_USERNAME: ${{secrets.SAUCE_USERNAME}} + # SAUCE_ACCESS_KEY: ${{secrets.SAUCE_ACCESS_KEY}} + # run: npm run test-browser + + - name: "Coverage Push" + uses: coverallsapp/github-action@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + flag-name: unit-${{ matrix.node }} + parallel: true + + - name: "Artifacts" + uses: actions/upload-artifact@v4 + if: matrix.node == 'node' + with: + name: distribution + path: dist/*.js + if-no-files-found: error + + + ghpages: + name: "GH Pages" + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + steps: + - name: "Checkout" + uses: actions/checkout@v4 + + - name: "Setup node" + uses: actions/setup-node@v4 + with: + node-version: latest + cache: npm + + - name: "npm ci" + run: npm ci + + - name: "GH Pages Prepare" + run: npm run ghpages + + - name: "GH Pages Push" + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{secrets.GITHUB_TOKEN}} + publish_dir: docs + + timezones: + name: "Timezones" + runs-on: ubuntu-latest + steps: + - name: "Checkout" + uses: actions/checkout@v4 + + - name: "Setup node" + uses: actions/setup-node@v4 + with: + node-version: latest + cache: npm + + - name: "npm ci" + run: npm ci + + - name: "Download tzdb" + id: tzdb + run: | + export TZDB_VERSION=$(node tools/scriptutils.js tzdb-version) + mkdir -p tools/tzdb + cd tools/tzdb + wget "https://data.iana.org/time-zones/releases/tzdata${TZDB_VERSION}.tar.gz" -O - | tar xz + + - name: "Build vzic" + run: | + git clone https://github.com/libical/vzic tools/vzic + cd tools/vzic + make TZID_PREFIX="" OLSON_DIR="$(readlink -f ../tzdb)" + + - name: "Run vzic" + run: tools/vzic/vzic --olson-dir tools/tzdb --output-dir tools/tzdb/zoneinfo + + - name: "Create zones" + run: | + mkdir -p dist + node tools/scriptutils.js generate-zones tools/tzdb > dist/ical.timezones.js + + - name: "Artifacts" + uses: actions/upload-artifact@v4 + with: + name: timezones + path: dist/ical.timezones.js + + finish: + needs: [build-and-test, timezones] + runs-on: ubuntu-latest + steps: + - name: Coveralls Finished + uses: coverallsapp/github-action@v2 + with: + github-token: ${{ secrets.github_token }} + parallel-finished: true diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..383842a --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,47 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + paths: + - '**/*.js' + schedule: + - cron: '22 18 * * 6' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: "javascript" + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/needinfo-remove.yml b/.github/workflows/needinfo-remove.yml new file mode 100644 index 0000000..5a7c282 --- /dev/null +++ b/.github/workflows/needinfo-remove.yml @@ -0,0 +1,25 @@ +--- +name: Remove needinfo label + +on: + issue_comment: + types: + - created + +jobs: + build: + runs-on: ubuntu-latest + if: | + github.event.comment.author_association != 'OWNER' && + github.event.comment.author_association != 'COLLABORATOR' + steps: + - name: Remove needinfo label + uses: octokit/request-action@v2.x + continue-on-error: true + with: + route: DELETE /repos/:repository/issues/:issue/labels/:label + repository: ${{ github.repository }} + issue: ${{ github.event.issue.number }} + label: needinfo + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/needinfo-stale.yml b/.github/workflows/needinfo-stale.yml new file mode 100644 index 0000000..05797be --- /dev/null +++ b/.github/workflows/needinfo-stale.yml @@ -0,0 +1,21 @@ +--- +name: Close old issues with the needinfo tag + +on: + schedule: + - cron: "0 0 * * *" + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Close old issues with the needinfo tag + uses: dwieeb/needs-reply@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + issue-label: needinfo + close-message: > + It looks like we haven't heard back on this issue, therefore we are + closing this issue. If this problem persists in the latest version + of ical.js, please re-open this issue. diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 0000000..9c332a1 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,53 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +--- + +name: "Publish" +on: + release: + types: [published] +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: "Checkout" + uses: actions/checkout@v4 + + - name: "Setup Node" + uses: actions/setup-node@v4 + with: + node-version: node + registry-url: "https://registry.npmjs.org" + + - name: "npm ci" + run: npm ci + + - name: "Build" + run: npm run build + + - name: "Linters" + run: npm run lint + + - name: "Node Unit Tests" + run: npm run test-unit + + - name: "Node Acceptance Tests" + run: npm run test-acceptance + + - name: "Attach release assets" + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release upload ${{ github.event.release.tag_name }} \ + dist/ical.js \ + dist/ical.min.js \ + dist/ical.es5.cjs \ + dist/ical.es5.min.cjs + + - name: "npm publish" + run: npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9d8ef38 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +node_modules +bower_components +*.pyc +dist/ +docs/api/ +docs/validator.html +docs/recur-tester.html +tools/vzic/ +tools/tzdb/ +tools/libical/ +tools/jsdoc-symbols-temp.json +coverage/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..7670b72 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,55 @@ +# Changelog + +## [Unreleased] + +## [1.3.0] - 2018-11-09 + +## [1.2.2] - 2016-07-20 + +## [1.2.1] - 2016-04-14 + +## [1.2.0] - 2016-04-13 + +## [1.1.2] - 2015-07-07 + +## [1.1.1] - 2015-06-01 + +## [1.1.0] - 2015-06-01 + +## [1.0.4] - 2015-05-23 + +## [1.0.3] - 2015-05-22 + +## [1.0.2] - 2015-05-22 + +## [1.0.1] - 2015-05-22 + +## [1.0.0] - 2015-05-22 + +## [0.0.6] - 2014-09-08 + +## [0.0.5] - 2014-09-03 + +## [0.0.4] - 2014-09-03 + +## [0.0.3] - 2014-09-03 + +## 0.0.1 - 2014-05-23 + +[Unreleased]: https://github.com/kewisch/ical.js/compare/v1.3.0...HEAD +[1.3.0]: https://github.com/kewisch/ical.js/compare/v1.2.2...v1.3.0 +[1.2.2]: https://github.com/kewisch/ical.js/compare/v1.2.1...v1.2.2 +[1.2.1]: https://github.com/kewisch/ical.js/compare/v1.2.0...v1.2.1 +[1.2.0]: https://github.com/kewisch/ical.js/compare/v1.1.2...v1.2.0 +[1.1.2]: https://github.com/kewisch/ical.js/compare/v1.1.1...v1.1.2 +[1.1.1]: https://github.com/kewisch/ical.js/compare/v1.1.0...v1.1.1 +[1.1.0]: https://github.com/kewisch/ical.js/compare/v1.0.4...v1.1.0 +[1.0.4]: https://github.com/kewisch/ical.js/compare/v1.0.3...v1.0.4 +[1.0.3]: https://github.com/kewisch/ical.js/compare/v1.0.2...v1.0.3 +[1.0.2]: https://github.com/kewisch/ical.js/compare/v1.0.1...v1.0.2 +[1.0.1]: https://github.com/kewisch/ical.js/compare/v1.0.0...v1.0.1 +[1.0.0]: https://github.com/kewisch/ical.js/compare/v0.0.6...v1.0.0 +[0.0.6]: https://github.com/kewisch/ical.js/compare/v0.0.5...v0.0.6 +[0.0.5]: https://github.com/kewisch/ical.js/compare/v0.0.4...v0.0.5 +[0.0.4]: https://github.com/kewisch/ical.js/compare/v0.0.3...v0.0.4 +[0.0.3]: https://github.com/kewisch/ical.js/compare/v0.0.1...v0.0.3 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..498baa3 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,15 @@ +# Community Participation Guidelines + +This repository is governed by Mozilla's code of conduct and etiquette guidelines. +For more details, please read the +[Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/). + +## How to Report +For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page. + + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..7f6ed5e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,46 @@ +Woohoo, a new contributor! +========================== +Thank you so much for looking into ical.js. With your work you are doing good by making it easier to +process calendar data on the web. + +To give you a feeling about what you are dealing with, ical.js was originally created as a +replacement for [libical], meant to be used in [Lightning], the calendaring extension to +Thunderbird. Using binary components in Mozilla extensions often leads to compatibility issues so a +pure JavaScript implementation was needed. It was also used in the Firefox OS calendaring +application. + +Work on the library prompted creating some standards around it. One of them is jCal ([rfc7265]), an +alternative text format for iCalendar data using JSON. The other document is jCard ([rfc7095]), +which is the counterpart for vCard data. + +Pull Requests +------------- +In general we are happy about any form of contribution to ical.js. Note however that since the +library is used in at least one larger projects, drastic changes to the API should be discussed in +an issue beforehand. If you have a bug fix that doesn't affect the API or just adds methods and you +don't want to waste time discussing it, feel free to just send a pull request and we'll see. + +Also, you should check for linter errors and run the tests using `npm run lint` `npm run test`. +There are also performance tests and browser tests if you want to be thourough. + +Currently the team working on ical.js consists of a very small number of voluntary contributors. If +you don't get a reply in a timely manner please don't feel turned down. If you are getting impatient +with us, go ahead and send one or more reminders via email or comment. + +License +------- +ical.js is licensed under the [Mozilla Public License], version 2.0. + +Last words +---------- +If you have any questions please don't hesitate to get in touch. You can leave a comment on an +issue, send [@kewisch] an email, or for ad-hoc questions contact `Fallen` on [chat.mozilla.org]. + +[libical]: https://github.com/libical/libical/ +[Lightning]: http://www.mozilla.org/projects/calendar/ +[rfc7095]: https://tools.ietf.org/html/rfc7095 +[rfc7265]: https://tools.ietf.org/html/rfc7265 +[running tests]: https://github.com/kewisch/ical.js/wiki/Running-Tests +[chat.mozilla.org]: https://chat.mozilla.org/ +[@kewisch]: https://github.com/kewisch/ +[Mozilla Public License]: https://www.mozilla.org/MPL/2.0/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a612ad9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/README.md b/README.md new file mode 100644 index 0000000..600a640 --- /dev/null +++ b/README.md @@ -0,0 +1,120 @@ +# ical.js - Javascript parser for iCalendar, jCal, vCard, jCard. + +This is a library to parse the formats defined in the following rfcs and their extensions: +* [rfc 5545](http://tools.ietf.org/html/rfc5545) (iCalendar) +* [rfc7265](http://tools.ietf.org/html/rfc7265) (jCal) +* [rfc6350](http://tools.ietf.org/html/rfc6350) (vCard) +* [rfc7095](http://tools.ietf.org/html/rfc7095) (jCard) + +The initial goal was to use it as a replacement for libical in the [Mozilla Calendar +Project](http://www.mozilla.org/projects/calendar/), but the library has been written with the web +in mind. This library enables you to do all sorts of cool experiments with calendar data and the +web. Most algorithms here were taken from [libical](https://github.com/libical/libical). If you are +bugfixing this library, please check if the fix can be upstreamed to libical. + +![Build Status](https://github.com/kewisch/ical.js/workflows/Checkin/badge.svg) [![Coverage Status](https://coveralls.io/repos/kewisch/ical.js/badge.svg)](https://coveralls.io/r/kewisch/ical.js) [![npm version](https://badge.fury.io/js/ical.js.svg)](http://badge.fury.io/js/ical.js) [![CDNJS](https://img.shields.io/cdnjs/v/ical.js.svg)](https://cdnjs.com/libraries/ical.js) + +## Sandbox and Validator + +If you want to try out ICAL.js right now, there is a +[jsfiddle](http://jsfiddle.net/kewisch/227efboL/) set up and ready to use. + +The ICAL validator demonstrates how to use the library in a webpage, and helps verify iCalendar and +jCal. [Try the validator online](http://kewisch.github.io/ical.js/validator.html) + +The recurrence tester calculates occurrences based on a RRULE. It can be used to aid in +creating test cases for the recurrence iterator. +[Try the recurrence tester online](https://kewisch.github.io/ical.js/recur-tester.html). + +## Installing + +ICAL.js has no dependencies and is written in modern JavaScript. You can install ICAL.js via +[npm](https://www.npmjs.com/), if you would like to use it in Node.js: +```bash +npm install ical.js +``` +Then simply import it for use: +```javascript +import ICAL from "ical.js"; +``` + +If you are working with a browser, be aware this is an ES6 module: + +```html + +``` + +If you need to make use of a script tag, you can use the transpiled ES5 version: +```html + + + +``` + +The browser examples above use the minified versions of the library, which is probably what you want. +However, there are also unminified versions of ICAL.js available on unpkg. + +- Unminified ES6 module: `https://unpkg.com/ical.js/dist/ical.js` +- Unminified ES5 version: `https://unpkg.com/ical.js/dist/ical.es5.cjs` + +## Timezones +The stock ical.js does not register any timezones, due to the additional size it brings. If you'd +like to do timezone conversion, and the timezone definitions are not included in the respective ics +files, you'll need to use `ical.timezones.js` or its minified counterpart. + +This file is not included in the distribution since it pulls in IANA timezones that might change +regularly. See the github actions on building your own timezones during CI, or grab a recent build +from main. + +## Documentation + +For a few guides with code samples, please check out +[the wiki](https://github.com/kewisch/ical.js/wiki). If you prefer, +full API documentation [is available here](http://kewisch.github.io/ical.js/api/). +If you are missing anything, please don't hesitate to create an issue. + +## Developing + +To contribute to ICAL.js you need to set up the development environment. A simple `npm install` will +get you set up. If you would like to help out and would like to discuss any API changes, please feel +free to create an issue. + +### Tests + +The following test suites are available + + npm run test-unit # Node unit tests + npm run test-acceptance # Node acceptance tests + npm run test-performance # Performance comparison tests + npm run test-browser # Browser unit and acceptance tests + + npm run test # Node unit and acceptance tests (This is fast and covers most aspects) + npm run test-all # All of the above + +See [the wiki](https://github.com/kewisch/ical.js/wiki/Running-Tests) for more details. + +Code coverage is automatically generated for the node unit tests. You can [view the coverage +results](https://coveralls.io/r/kewisch/ical.js) online, or run them locally to make sure new +code is covered. + +### Linters +To make sure all ICAL.js code uses a common style, please run the linters using `npm run lint`. +Please make sure you fix any issues shown by this command before sending a pull request. + +### Documentation +You can generate the documentation locally, this is also helpful to ensure the jsdoc you have +written is valid. To do so, run `npm run jsdoc`. You will find the output in the `docs/api/` +subdirectory. + +### Packaging +When you are done with your work, you can run `npm run build` to create the single-file build for +use in the browser, including its minified counterpart and the source map. + +## License +ical.js is licensed under the +[Mozilla Public License](https://www.mozilla.org/MPL/2.0/), version 2.0. diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..bf37c40 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..d787180 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,389 @@ +import js from "@eslint/js"; +import globals from "globals"; +import stylistic from '@stylistic/eslint-plugin'; +import html from "eslint-plugin-html"; + +export default [ + { + ignores: [ + "**/*.{js,cjs}", + "!lib/ical/**/*.{js,cjs}", + "!test/**/*.{js,cjs}", + "!tools/scriptutils.js", + "!tools/ICALTester/**/*.js", + "!tools/jsdoc-ical.cjs", + "!eslint.config.js", + "!rollup.config.js", + "!karma.conf.cjs" + ] + }, + js.configs.recommended, + { + plugins: { + "@stylistic": stylistic + }, + languageOptions: { + globals: { + ...globals.es2021 + } + }, + rules: { + // Enforce one true brace style (opening brace on the same line) + // Allow single line (for now) because of the vast number of changes needed + "@stylistic/brace-style": ["error", "1tbs", { allowSingleLine: true }], + + // Enforce newline at the end of file, with no multiple empty lines. + "@stylistic/eol-last": "error", + + // Disallow using variables outside the blocks they are defined + //"block-scoped-var": "error", + + // Allow trailing commas for easy list extension. Having them does not + // impair readability, but also not required either. + "@stylistic/comma-dangle": 0, + + // Enforce spacing before and after comma + "@stylistic/comma-spacing": ["error", { before: false, after: true }], + + // Enforce one true comma style. + "@stylistic/comma-style": ["error", "last"], + + // Enforce curly brace conventions for all control statements. + //"curly": "error", + + // Enforce the spacing around the * in generator functions. + "@stylistic/generator-star-spacing": ["error", "after"], + + // Require space before/after arrow function's arrow + "@stylistic/arrow-spacing": ["error", { before: true, after: true }], + + // Enforces spacing between keys and values in object literal properties. + "@stylistic/key-spacing": ["error", { beforeColon: false, afterColon: true, mode: "minimum" }], + + // Disallow the omission of parentheses when invoking a constructor with no + // arguments. + "@stylistic/new-parens": "error", + + // Disallow use of the Array constructor. + "no-array-constructor": "error", + + // disallow use of the Object constructor + "no-object-constructor": "error", + + // Disallow Primitive Wrapper Instances + "no-new-wrappers": "error", + + // Disallow adding to native types + "no-extend-native": "error", + + // Disallow unnecessary semicolons. + "@stylistic/no-extra-semi": "error", + + // Disallow mixed spaces and tabs for indentation. + "@stylistic/no-mixed-spaces-and-tabs": "error", + + // Disallow comparisons where both sides are exactly the same. + "no-self-compare": "error", + + // Disallow trailing whitespace at the end of lines. + "@stylistic/no-trailing-spaces": "error", + + // disallow use of octal escape sequences in string literals, such as + // var foo = "Copyright \251"; + "no-octal-escape": "error", + + // disallow use of void operator + "no-void": "error", + + // Disallow Yoda conditions (where literal value comes first). + "yoda": "error", + + // Require a space immediately following the // in a line comment. + //"spaced-comment": ["error", "always"], + + // Require use of the second argument for parseInt(). + //"radix": "error", + + // Require spaces before/after unary operators (words on by default, + // nonwords off by default). + //"space-unary-ops": ["error", { "words": true, "nonwords": false }], + + // Enforce spacing after semicolons. + "@stylistic/semi-spacing": ["error", { before: false, after: true }], + + // Disallow the use of Boolean literals in conditional expressions. + "no-unneeded-ternary": "error", + + // Disallow use of multiple spaces (sometimes used to align const values, + // array or object items, etc.). It's hard to maintain and doesn't add that + // much benefit. + "@stylistic/no-multi-spaces": "error", + + // Require spaces around operators, except for a|0. + // Disabled for now given eslint doesn't support default args yet + // "space-infix-ops": ["error", {"int32Hint": true}], + + // Require a space around all keywords. + "@stylistic/keyword-spacing": "error", + + // Disallow space between function identifier and application. + "@stylistic/func-call-spacing": "error", + + // Disallow use of comma operator. + "no-sequences": "error", + + // Disallow use of assignment in return statement. It is preferable for a + // single line of code to have only one easily predictable effect. + "no-return-assign": "error", + + // Require return statements to either always or never specify values + //"consistent-return": "error", + + // Disallow padding within blocks. + //"padded-blocks": ["error", "never"], + + // Disallow spaces inside parentheses. + "@stylistic/space-in-parens": ["error", "never"], + + // Require space after keyword for anonymous functions, but disallow space + // after name of named functions. + "@stylistic/space-before-function-paren": ["error", { anonymous: "never", named: "never" }], + + // Always require use of semicolons wherever they are valid. + "@stylistic/semi": ["error", "always"], + + // Warn about declaration of variables already declared in the outer scope. + "no-shadow": "error", + + // Disallow global and local variables that aren't used, but allow unused function arguments. + "no-unused-vars": ["error", { vars: "all", args: "none" }], + + // Require padding inside curly braces + "@stylistic/object-curly-spacing": ["error", "always"], + + // Disallow spaces inside of brackets + "@stylistic/array-bracket-spacing": ["error", "never"], + + // Disallow constant expressions in conditions + //"no-constant-condition": ["error", {"checkLoops": false }], + + // Disallow Regexs That Look Like Division + "no-div-regex": "error", + + // Disallow Iterator (using __iterator__) + "no-iterator": "error", + + // Enforce consistent linebreak style + "@stylistic/linebreak-style": ["error", "unix"], + + // Enforces return statements in callbacks of array's methods + "array-callback-return": "error", + + // Disallow duplicate imports + "no-duplicate-imports": "error", + + // Disallow Labeled Statements + "no-labels": "error", + + // Disallow Multiline Strings + "no-multi-str": "error", + + // Disallow Initializing to undefined + "no-undef-init": "error", + + // Disallow unnecessary computed property keys on objects + "no-useless-computed-key": "error", + + // Disallow unnecessary constructor + "no-useless-constructor": "error", + + // Disallow renaming import, export, and destructured assignments to the + // same name + "no-useless-rename": "error", + + // Enforce spacing between rest and spread operators and their expressions + "@stylistic/rest-spread-spacing": ["error", "never"], + + // Disallow usage of spacing in template string expressions + "@stylistic/template-curly-spacing": ["error", "never"], + + // Disallow the Unicode Byte Order Mark + "unicode-bom": ["error", "never"], + + // Enforce spacing around the * in yield* expressions + "@stylistic/yield-star-spacing": ["error", "after"], + + // Disallow Implied eval + "no-implied-eval": "error", + + // Disallow unnecessary function binding + "no-extra-bind": "error", + + // Disallow new For Side Effects + "no-new": "error", + + // Require IIFEs to be Wrapped + //"wrap-iife": ["error", "inside"], + + // Disallow Unused Expressions + "no-unused-expressions": "error", + + // Disallow function or var declarations in nested blocks + "no-inner-declarations": "error", + + // Enforce newline before and after dot + "@stylistic/dot-location": ["error", "property"], + + // Disallow Use of caller/callee + "no-caller": "error", + + // Disallow Floating Decimals + "@stylistic/no-floating-decimal": "error", + + // Require Space Before Blocks + "@stylistic/space-before-blocks": "error", + + // Operators always before the line break + "@stylistic/operator-linebreak": ["error", "after", { overrides: { ":": "before", "?": "ignore" } }], + + // Restricts the use of parentheses to only where they are necessary + //"no-extra-parens": ["error", "all", { "conditionalAssign": false, "returnAssign": false, "nestedBinaryExpressions": false }], + + // Disallow if as the only statement in an else block. + //"no-lonely-if": "error", + + // Not more than two empty lines with in the file, and no extra lines at + // beginning or end of file. + "@stylistic/no-multiple-empty-lines": ["error", { max: 2, maxEOF: 0, maxBOF: 0 }], + + // Make sure all setters have a corresponding getter + "accessor-pairs": "error", + + // Enforce spaces inside of single line blocks + //"block-spacing": ["error", "always"], + + // Disallow spaces inside of computed properties + "@stylistic/computed-property-spacing": ["error", "never"], + + // Require consistent this (using |self|) + "consistent-this": ["error", "self"], + + // Disallow unnecessary .call() and .apply() + "no-useless-call": "error", + + // Require dot notation when accessing properties + "dot-notation": "error", + + // Disallow named function expressions + //"func-names": ["error", "never"], + + // Enforce placing object properties on separate lines + "@stylistic/object-property-newline": ["error", { allowMultiplePropertiesPerLine: true }], + + // Enforce consistent line breaks inside braces + //"object-curly-newline": ["error", { "multiline": true }], + + // Disallow whitespace before properties + "@stylistic/no-whitespace-before-property": "error", + + // Disallow mixes of different operators, but allow simple math operations. + //"no-mixed-operators": ["error", { + // "groups": [ + // /* ["+", "-", "*", "/", "%", "**"], */ + // ["&", "|", "^", "~", "<<", ">>", ">>>"], + // ["==", "!=", "===", "!==", ">", ">=", "<", "<="], + // ["&&", "||"], + // ["in", "instanceof"] + // ] + //}], + + // Disallow unnecessary concatenation of strings + "no-useless-concat": "error", + + // Disallow unmodified conditions of loops + //"no-unmodified-loop-condition": "error", + + // Suggest using arrow functions as callbacks + //"prefer-arrow-callback": ["error", { "allowNamedFunctions": true }], + + // Suggest using the spread operator instead of .apply() + "prefer-spread": "error", + + // Quoting style for property names + //"@stylistic/quote-props": ["error", "consistent-as-needed", { "keywords": true }], + + // Disallow negated conditions + //"no-negated-condition": "error", + + // Enforce a maximum number of statements allowed per line + "@stylistic/max-statements-per-line": ["error", { max: 2 }], + + // Disallow arrow functions where they could be confused with comparisons + "@stylistic/no-confusing-arrow": "error", + + // Disallow Unnecessary Nested Blocks + "no-lone-blocks": "error", + + // Enforce consistent indentation (2-space) + //"indent": ["error", 2, { "SwitchCase": 1 }], + + // Disallow var, use let or const instead + "no-var": "error" + } + }, + { + files: ["test/**/*.js"], + languageOptions: { + globals: { + ...globals.mocha, + ...globals.node, + ICAL: "readonly", + assert: "readonly", + testSupport: "readonly", + perfTest: "readonly" + } + } + }, + { + files: ["test/performance/**/*.js"], + languageOptions: { + globals: { + perfCompareSuite: "readonly" + } + } + }, + { + files: ["test/support/helper.js"], + languageOptions: { + globals: { + ...globals.browser, + ...globals.node, + ...globals.mocha + } + } + }, + { + files: ["tools/scriptutils.js", "test/support/perfReporter.cjs", "karma.conf.cjs", "tools/ICALTester/**/*.js"], + languageOptions: { + globals: globals.node + } + }, + { + files: ["eslint.config.js"], + plugins: { + "@stylistic": stylistic + }, + rules: { + "@stylistic/quote-props": ["error", "consistent-as-needed"] + } + }, + { + files: ["tools/**/*.html"], + plugins: { + "@html": html + }, + languageOptions: { + globals: globals.browser + } + } +]; diff --git a/jsdoc-prepare.json b/jsdoc-prepare.json new file mode 100644 index 0000000..f95116e --- /dev/null +++ b/jsdoc-prepare.json @@ -0,0 +1,10 @@ +{ + "source": { + "include": "lib/ical", + "includePattern": ".js$" + }, + "plugins": ["tools/jsdoc-collect-types.cjs", "node_modules/jsdoc-tsimport-plugin/index.js"], + "opts": { + "destination": "docs/api/" + } +} diff --git a/jsdoc.json b/jsdoc.json new file mode 100644 index 0000000..fdb0a6f --- /dev/null +++ b/jsdoc.json @@ -0,0 +1,20 @@ +{ + "source": { + "include": "lib/ical", + "includePattern": ".js$" + }, + "plugins": ["plugins/markdown", "tools/jsdoc-ical.cjs", "node_modules/jsdoc-tsimport-plugin/index.js"], + "opts": { + "encoding": "utf8", + "readme": "README.md", + "destination": "docs/api/", + "template": "node_modules/clean-jsdoc-theme", + "theme_opts": { + "default_theme": "light" + } + }, + "markdown": { + "hardwrap": false, + "idInHeadings": true + } +} diff --git a/karma.conf.cjs b/karma.conf.cjs new file mode 100644 index 0000000..0627e3e --- /dev/null +++ b/karma.conf.cjs @@ -0,0 +1,48 @@ +// Karma configuration +// Generated on Sun Feb 20 2022 00:57:11 GMT+0100 (Central European Standard Time) + +let pkg = require("./package.json"); + +module.exports = function(config) { + config.set({ + basePath: '', + frameworks: ['mocha', 'chai'], + plugins: ["karma-chai", "karma-mocha", "karma-spec-reporter"], + files: [ + { pattern: 'samples/**/*.ics', included: false }, + { pattern: 'test/parser/*', included: false }, + { pattern: 'lib/ical/*.js', type: 'module', included: false }, + { pattern: 'test/*_test.js', included: false }, + { pattern: 'test/acceptance/*_test.js', included: false }, + { pattern: 'test/support/helper.js', type: "module", included: true }, + ], + client: { mocha: Object.assign(pkg.mocha, { timeout: 0 }) }, + reporters: ['spec'], + port: 9876, + colors: true, + autoWatch: false, + singleRun: true, + concurrency: Infinity, + captureTimeout: 240000, + browserNoActivityTimeout: 120000, + //browsers: ['Firefox'], + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO + }); + + + if (process.env.GITHUB_ACTIONS) { + config.set({ + exitOnFailure: false, + customLaunchers: pkg.saucelabs, + browsers: Object.keys(pkg.saucelabs), + reporters: ['saucelabs', 'spec'], + sauceLabs: { + testName: 'ICAL.js', + startConnect: true + } + }); + } +}; diff --git a/lib/ical/binary.js b/lib/ical/binary.js new file mode 100644 index 0000000..57c9dee --- /dev/null +++ b/lib/ical/binary.js @@ -0,0 +1,173 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */ + +/** + * Represents the BINARY value type, which contains extra methods for encoding and decoding. + * + * @memberof ICAL + */ +class Binary { + /** + * Creates a binary value from the given string. + * + * @param {String} aString The binary value string + * @return {Binary} The binary value instance + */ + static fromString(aString) { + return new Binary(aString); + } + + /** + * Creates a new ICAL.Binary instance + * + * @param {String} aValue The binary data for this value + */ + constructor(aValue) { + this.value = aValue; + } + + /** + * The type name, to be used in the jCal object. + * @default "binary" + * @constant + */ + icaltype = "binary"; + + /** + * Base64 decode the current value + * + * @return {String} The base64-decoded value + */ + decodeValue() { + return this._b64_decode(this.value); + } + + /** + * Encodes the passed parameter with base64 and sets the internal + * value to the result. + * + * @param {String} aValue The raw binary value to encode + */ + setEncodedValue(aValue) { + this.value = this._b64_encode(aValue); + } + + _b64_encode(data) { + // http://kevin.vanzonneveld.net + // + original by: Tyler Akins (http://rumkin.com) + // + improved by: Bayron Guevara + // + improved by: Thunder.m + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + bugfixed by: Pellentesque Malesuada + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + improved by: Rafał Kukawski (http://kukawski.pl) + // * example 1: base64_encode('Kevin van Zonneveld'); + // * returns 1: 'S2V2aW4gdmFuIFpvbm5ldmVsZA==' + // mozilla has this native + // - but breaks in 2.0.0.12! + //if (typeof this.window['atob'] == 'function') { + // return atob(data); + //} + let b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + + "abcdefghijklmnopqrstuvwxyz0123456789+/="; + let o1, o2, o3, h1, h2, h3, h4, bits, i = 0, + ac = 0, + enc = "", + tmp_arr = []; + + if (!data) { + return data; + } + + do { // pack three octets into four hexets + o1 = data.charCodeAt(i++); + o2 = data.charCodeAt(i++); + o3 = data.charCodeAt(i++); + + bits = o1 << 16 | o2 << 8 | o3; + + h1 = bits >> 18 & 0x3f; + h2 = bits >> 12 & 0x3f; + h3 = bits >> 6 & 0x3f; + h4 = bits & 0x3f; + + // use hexets to index into b64, and append result to encoded string + tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4); + } while (i < data.length); + + enc = tmp_arr.join(''); + + let r = data.length % 3; + + return (r ? enc.slice(0, r - 3) : enc) + '==='.slice(r || 3); + + } + + _b64_decode(data) { + // http://kevin.vanzonneveld.net + // + original by: Tyler Akins (http://rumkin.com) + // + improved by: Thunder.m + // + input by: Aman Gupta + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + bugfixed by: Onno Marsman + // + bugfixed by: Pellentesque Malesuada + // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // + input by: Brett Zamir (http://brett-zamir.me) + // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + // * example 1: base64_decode('S2V2aW4gdmFuIFpvbm5ldmVsZA=='); + // * returns 1: 'Kevin van Zonneveld' + // mozilla has this native + // - but breaks in 2.0.0.12! + //if (typeof this.window['btoa'] == 'function') { + // return btoa(data); + //} + let b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + + "abcdefghijklmnopqrstuvwxyz0123456789+/="; + let o1, o2, o3, h1, h2, h3, h4, bits, i = 0, + ac = 0, + dec = "", + tmp_arr = []; + + if (!data) { + return data; + } + + data += ''; + + do { // unpack four hexets into three octets using index points in b64 + h1 = b64.indexOf(data.charAt(i++)); + h2 = b64.indexOf(data.charAt(i++)); + h3 = b64.indexOf(data.charAt(i++)); + h4 = b64.indexOf(data.charAt(i++)); + + bits = h1 << 18 | h2 << 12 | h3 << 6 | h4; + + o1 = bits >> 16 & 0xff; + o2 = bits >> 8 & 0xff; + o3 = bits & 0xff; + + if (h3 == 64) { + tmp_arr[ac++] = String.fromCharCode(o1); + } else if (h4 == 64) { + tmp_arr[ac++] = String.fromCharCode(o1, o2); + } else { + tmp_arr[ac++] = String.fromCharCode(o1, o2, o3); + } + } while (i < data.length); + + dec = tmp_arr.join(''); + + return dec; + } + + /** + * The string representation of this value + * @return {String} + */ + toString() { + return this.value; + } +} +export default Binary; diff --git a/lib/ical/component.js b/lib/ical/component.js new file mode 100644 index 0000000..76e3e73 --- /dev/null +++ b/lib/ical/component.js @@ -0,0 +1,621 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */ + +import Property from "./property.js"; +import Timezone from "./timezone.js"; +import ICALParse from "./parse.js"; +import stringify from "./stringify.js"; +import design from "./design.js"; +// needed for typescript type resolution +// eslint-disable-next-line no-unused-vars +import Duration from "./duration.js"; +// needed for typescript type resolution +// eslint-disable-next-line no-unused-vars +import UtcOffset from "./utc_offset.js"; +// needed for typescript type resolution +// eslint-disable-next-line no-unused-vars +import Binary from "./binary.js"; +// needed for typescript type resolution +// eslint-disable-next-line no-unused-vars +import Period from "./period.js"; +// needed for typescript type resolution +// eslint-disable-next-line no-unused-vars +import Recur from "./recur.js"; +// needed for typescript type resolution +// eslint-disable-next-line no-unused-vars +import Time from "./time.js"; + +/** + * This lets typescript resolve our custom types in the + * generated d.ts files (jsdoc typedefs are converted to typescript types). + * Ignore prevents the typedefs from being documented more than once. + * @ignore + * @typedef {import("./types.js").designSet} designSet + * Imports the 'designSet' type from the "types.js" module + * @typedef {import("./types.js").Geo} Geo + * Imports the 'Geo' type from the "types.js" module + */ + +const NAME_INDEX = 0; +const PROPERTY_INDEX = 1; +const COMPONENT_INDEX = 2; + +/** + * Wraps a jCal component, adding convenience methods to add, remove and update subcomponents and + * properties. + * + * @memberof ICAL + */ +class Component { + /** + * Create an {@link ICAL.Component} by parsing the passed iCalendar string. + * + * @param {String} str The iCalendar string to parse + */ + static fromString(str) { + return new Component(ICALParse.component(str)); + } + + /** + * Creates a new Component instance. + * + * @param {Array|String} jCal Raw jCal component data OR name of new + * component + * @param {Component=} parent Parent component to associate + */ + constructor(jCal, parent) { + if (typeof(jCal) === 'string') { + // jCal spec (name, properties, components) + jCal = [jCal, [], []]; + } + + // mostly for legacy reasons. + this.jCal = jCal; + + this.parent = parent || null; + + if (!this.parent && this.name === 'vcalendar') { + this._timezoneCache = new Map(); + } + } + + /** + * Hydrated properties are inserted into the _properties array at the same + * position as in the jCal array, so it is possible that the array contains + * undefined values for unhydrdated properties. To avoid iterating the + * array when checking if all properties have been hydrated, we save the + * count here. + * + * @type {Number} + * @private + */ + _hydratedPropertyCount = 0; + + /** + * The same count as for _hydratedPropertyCount, but for subcomponents + * + * @type {Number} + * @private + */ + _hydratedComponentCount = 0; + + /** + * A cache of hydrated time zone objects which may be used by consumers, keyed + * by time zone ID. + * + * @type {Map} + * @private + */ + _timezoneCache = null; + + /** + * @private + */ + _components = null; + + /** + * @private + */ + _properties = null; + + /** + * The name of this component + * + * @type {String} + */ + get name() { + return this.jCal[NAME_INDEX]; + } + + /** + * The design set for this component, e.g. icalendar vs vcard + * + * @type {designSet} + * @private + */ + get _designSet() { + let parentDesign = this.parent && this.parent._designSet; + return parentDesign || design.getDesignSet(this.name); + } + + /** + * @private + */ + _hydrateComponent(index) { + if (!this._components) { + this._components = []; + this._hydratedComponentCount = 0; + } + + if (this._components[index]) { + return this._components[index]; + } + + let comp = new Component( + this.jCal[COMPONENT_INDEX][index], + this + ); + + this._hydratedComponentCount++; + return (this._components[index] = comp); + } + + /** + * @private + */ + _hydrateProperty(index) { + if (!this._properties) { + this._properties = []; + this._hydratedPropertyCount = 0; + } + + if (this._properties[index]) { + return this._properties[index]; + } + + let prop = new Property( + this.jCal[PROPERTY_INDEX][index], + this + ); + + this._hydratedPropertyCount++; + return (this._properties[index] = prop); + } + + /** + * Finds first sub component, optionally filtered by name. + * + * @param {String=} name Optional name to filter by + * @return {?Component} The found subcomponent + */ + getFirstSubcomponent(name) { + if (name) { + let i = 0; + let comps = this.jCal[COMPONENT_INDEX]; + let len = comps.length; + + for (; i < len; i++) { + if (comps[i][NAME_INDEX] === name) { + let result = this._hydrateComponent(i); + return result; + } + } + } else { + if (this.jCal[COMPONENT_INDEX].length) { + return this._hydrateComponent(0); + } + } + + // ensure we return a value (strict mode) + return null; + } + + /** + * Finds all sub components, optionally filtering by name. + * + * @param {String=} name Optional name to filter by + * @return {Component[]} The found sub components + */ + getAllSubcomponents(name) { + let jCalLen = this.jCal[COMPONENT_INDEX].length; + let i = 0; + + if (name) { + let comps = this.jCal[COMPONENT_INDEX]; + let result = []; + + for (; i < jCalLen; i++) { + if (name === comps[i][NAME_INDEX]) { + result.push( + this._hydrateComponent(i) + ); + } + } + return result; + } else { + if (!this._components || + (this._hydratedComponentCount !== jCalLen)) { + for (; i < jCalLen; i++) { + this._hydrateComponent(i); + } + } + + return this._components || []; + } + } + + /** + * Returns true when a named property exists. + * + * @param {String} name The property name + * @return {Boolean} True, when property is found + */ + hasProperty(name) { + let props = this.jCal[PROPERTY_INDEX]; + let len = props.length; + + let i = 0; + for (; i < len; i++) { + // 0 is property name + if (props[i][NAME_INDEX] === name) { + return true; + } + } + + return false; + } + + /** + * Finds the first property, optionally with the given name. + * + * @param {String=} name Lowercase property name + * @return {?Property} The found property + */ + getFirstProperty(name) { + if (name) { + let i = 0; + let props = this.jCal[PROPERTY_INDEX]; + let len = props.length; + + for (; i < len; i++) { + if (props[i][NAME_INDEX] === name) { + let result = this._hydrateProperty(i); + return result; + } + } + } else { + if (this.jCal[PROPERTY_INDEX].length) { + return this._hydrateProperty(0); + } + } + + return null; + } + + /** + * Returns first property's value, if available. + * + * @param {String=} name Lowercase property name + * @return {Binary | Duration | Period | + * Recur | Time | UtcOffset | Geo | string | null} The found property value. + */ + getFirstPropertyValue(name) { + let prop = this.getFirstProperty(name); + if (prop) { + return prop.getFirstValue(); + } + + return null; + } + + /** + * Get all properties in the component, optionally filtered by name. + * + * @param {String=} name Lowercase property name + * @return {Property[]} List of properties + */ + getAllProperties(name) { + let jCalLen = this.jCal[PROPERTY_INDEX].length; + let i = 0; + + if (name) { + let props = this.jCal[PROPERTY_INDEX]; + let result = []; + + for (; i < jCalLen; i++) { + if (name === props[i][NAME_INDEX]) { + result.push( + this._hydrateProperty(i) + ); + } + } + return result; + } else { + if (!this._properties || + (this._hydratedPropertyCount !== jCalLen)) { + for (; i < jCalLen; i++) { + this._hydrateProperty(i); + } + } + + return this._properties || []; + } + } + + /** + * @private + */ + _removeObjectByIndex(jCalIndex, cache, index) { + cache = cache || []; + // remove cached version + if (cache[index]) { + let obj = cache[index]; + if ("parent" in obj) { + obj.parent = null; + } + } + + cache.splice(index, 1); + + // remove it from the jCal + this.jCal[jCalIndex].splice(index, 1); + } + + /** + * @private + */ + _removeObject(jCalIndex, cache, nameOrObject) { + let i = 0; + let objects = this.jCal[jCalIndex]; + let len = objects.length; + let cached = this[cache]; + + if (typeof(nameOrObject) === 'string') { + for (; i < len; i++) { + if (objects[i][NAME_INDEX] === nameOrObject) { + this._removeObjectByIndex(jCalIndex, cached, i); + return true; + } + } + } else if (cached) { + for (; i < len; i++) { + if (cached[i] && cached[i] === nameOrObject) { + this._removeObjectByIndex(jCalIndex, cached, i); + return true; + } + } + } + + return false; + } + + /** + * @private + */ + _removeAllObjects(jCalIndex, cache, name) { + let cached = this[cache]; + + // Unfortunately we have to run through all children to reset their + // parent property. + let objects = this.jCal[jCalIndex]; + let i = objects.length - 1; + + // descending search required because splice + // is used and will effect the indices. + for (; i >= 0; i--) { + if (!name || objects[i][NAME_INDEX] === name) { + this._removeObjectByIndex(jCalIndex, cached, i); + } + } + } + + /** + * Adds a single sub component. + * + * @param {Component} component The component to add + * @return {Component} The passed in component + */ + addSubcomponent(component) { + if (!this._components) { + this._components = []; + this._hydratedComponentCount = 0; + } + + if (component.parent) { + component.parent.removeSubcomponent(component); + } + + let idx = this.jCal[COMPONENT_INDEX].push(component.jCal); + this._components[idx - 1] = component; + this._hydratedComponentCount++; + component.parent = this; + return component; + } + + /** + * Removes a single component by name or the instance of a specific + * component. + * + * @param {Component|String} nameOrComp Name of component, or component + * @return {Boolean} True when comp is removed + */ + removeSubcomponent(nameOrComp) { + let removed = this._removeObject(COMPONENT_INDEX, '_components', nameOrComp); + if (removed) { + this._hydratedComponentCount--; + } + return removed; + } + + /** + * Removes all components or (if given) all components by a particular + * name. + * + * @param {String=} name Lowercase component name + */ + removeAllSubcomponents(name) { + let removed = this._removeAllObjects(COMPONENT_INDEX, '_components', name); + this._hydratedComponentCount = 0; + return removed; + } + + /** + * Adds an {@link ICAL.Property} to the component. + * + * @param {Property} property The property to add + * @return {Property} The passed in property + */ + addProperty(property) { + if (!(property instanceof Property)) { + throw new TypeError('must be instance of ICAL.Property'); + } + + if (!this._properties) { + this._properties = []; + this._hydratedPropertyCount = 0; + } + + if (property.parent) { + property.parent.removeProperty(property); + } + + let idx = this.jCal[PROPERTY_INDEX].push(property.jCal); + this._properties[idx - 1] = property; + this._hydratedPropertyCount++; + property.parent = this; + return property; + } + + /** + * Helper method to add a property with a value to the component. + * + * @param {String} name Property name to add + * @param {String|Number|Object} value Property value + * @return {Property} The created property + */ + addPropertyWithValue(name, value) { + let prop = new Property(name); + prop.setValue(value); + + this.addProperty(prop); + + return prop; + } + + /** + * Helper method that will update or create a property of the given name + * and sets its value. If multiple properties with the given name exist, + * only the first is updated. + * + * @param {String} name Property name to update + * @param {String|Number|Object} value Property value + * @return {Property} The created property + */ + updatePropertyWithValue(name, value) { + let prop = this.getFirstProperty(name); + + if (prop) { + prop.setValue(value); + } else { + prop = this.addPropertyWithValue(name, value); + } + + return prop; + } + + /** + * Removes a single property by name or the instance of the specific + * property. + * + * @param {String|Property} nameOrProp Property name or instance to remove + * @return {Boolean} True, when deleted + */ + removeProperty(nameOrProp) { + let removed = this._removeObject(PROPERTY_INDEX, '_properties', nameOrProp); + if (removed) { + this._hydratedPropertyCount--; + } + return removed; + } + + /** + * Removes all properties associated with this component, optionally + * filtered by name. + * + * @param {String=} name Lowercase property name + * @return {Boolean} True, when deleted + */ + removeAllProperties(name) { + let removed = this._removeAllObjects(PROPERTY_INDEX, '_properties', name); + this._hydratedPropertyCount = 0; + return removed; + } + + /** + * Returns the Object representation of this component. The returned object + * is a live jCal object and should be cloned if modified. + * @return {Object} + */ + toJSON() { + return this.jCal; + } + + /** + * The string representation of this component. + * @return {String} + */ + toString() { + return stringify.component( + this.jCal, this._designSet + ); + } + + /** + * Retrieve a time zone definition from the component tree, if any is present. + * If the tree contains no time zone definitions or the TZID cannot be + * matched, returns null. + * + * @param {String} tzid The ID of the time zone to retrieve + * @return {Timezone} The time zone corresponding to the ID, or null + */ + getTimeZoneByID(tzid) { + // VTIMEZONE components can only appear as a child of the VCALENDAR + // component; walk the tree if we're not the root. + if (this.parent) { + return this.parent.getTimeZoneByID(tzid); + } + + // If there is no time zone cache, we are probably parsing an incomplete + // file and will have no time zone definitions. + if (!this._timezoneCache) { + return null; + } + + if (this._timezoneCache.has(tzid)) { + return this._timezoneCache.get(tzid); + } + + // If the time zone is not already cached, hydrate it from the + // subcomponents. + const zones = this.getAllSubcomponents('vtimezone'); + for (const zone of zones) { + if (zone.getFirstProperty('tzid').getFirstValue() === tzid) { + const hydratedZone = new Timezone({ + component: zone, + tzid: tzid, + }); + + this._timezoneCache.set(tzid, hydratedZone); + + return hydratedZone; + } + } + + // Per the standard, we should always have a time zone defined in a file + // for any referenced TZID, but don't blow up if the file is invalid. + return null; + } +} +export default Component; diff --git a/lib/ical/component_parser.js b/lib/ical/component_parser.js new file mode 100644 index 0000000..71f1575 --- /dev/null +++ b/lib/ical/component_parser.js @@ -0,0 +1,157 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */ + +import ICALParse from "./parse.js"; +import Component from "./component.js"; +import Event from "./event.js"; +import Timezone from "./timezone.js"; + +/** + * The ComponentParser is used to process a String or jCal Object, + * firing callbacks for various found components, as well as completion. + * + * @example + * var options = { + * // when false no events will be emitted for type + * parseEvent: true, + * parseTimezone: true + * }; + * + * var parser = new ICAL.ComponentParser(options); + * + * parser.onevent(eventComponent) { + * //... + * } + * + * // ontimezone, etc... + * + * parser.oncomplete = function() { + * + * }; + * + * parser.process(stringOrComponent); + * + * @memberof ICAL + */ +class ComponentParser { + /** + * Creates a new ICAL.ComponentParser instance. + * + * @param {Object=} options Component parser options + * @param {Boolean} options.parseEvent Whether events should be parsed + * @param {Boolean} options.parseTimezeone Whether timezones should be parsed + */ + constructor(options) { + if (typeof(options) === 'undefined') { + options = {}; + } + + for (let [key, value] of Object.entries(options)) { + this[key] = value; + } + } + + /** + * When true, parse events + * + * @type {Boolean} + */ + parseEvent = true; + + /** + * When true, parse timezones + * + * @type {Boolean} + */ + parseTimezone = true; + + + /* SAX like events here for reference */ + + /** + * Fired when parsing is complete + * @callback + */ + oncomplete = /* c8 ignore next */ function() {}; + + /** + * Fired if an error occurs during parsing. + * + * @callback + * @param {Error} err details of error + */ + onerror = /* c8 ignore next */ function(err) {}; + + /** + * Fired when a top level component (VTIMEZONE) is found + * + * @callback + * @param {Timezone} component Timezone object + */ + ontimezone = /* c8 ignore next */ function(component) {}; + + /** + * Fired when a top level component (VEVENT) is found. + * + * @callback + * @param {Event} component Top level component + */ + onevent = /* c8 ignore next */ function(component) {}; + + /** + * Process a string or parse ical object. This function itself will return + * nothing but will start the parsing process. + * + * Events must be registered prior to calling this method. + * + * @param {Component|String|Object} ical The component to process, + * either in its final form, as a jCal Object, or string representation + */ + process(ical) { + //TODO: this is sync now in the future we will have a incremental parser. + if (typeof(ical) === 'string') { + ical = ICALParse(ical); + } + + if (!(ical instanceof Component)) { + ical = new Component(ical); + } + + let components = ical.getAllSubcomponents(); + let i = 0; + let len = components.length; + let component; + + for (; i < len; i++) { + component = components[i]; + + switch (component.name) { + case 'vtimezone': + if (this.parseTimezone) { + let tzid = component.getFirstPropertyValue('tzid'); + if (tzid) { + this.ontimezone(new Timezone({ + tzid: tzid, + component: component + })); + } + } + break; + case 'vevent': + if (this.parseEvent) { + this.onevent(new Event(component)); + } + break; + default: + continue; + } + } + + //XXX: ideally we should do a "nextTick" here + // so in all cases this is actually async. + this.oncomplete(); + } +} +export default ComponentParser; diff --git a/lib/ical/design.js b/lib/ical/design.js new file mode 100644 index 0000000..e87959d --- /dev/null +++ b/lib/ical/design.js @@ -0,0 +1,1026 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */ + +import { isStrictlyNaN, extend } from "./helpers.js"; +import UtcOffset from "./utc_offset.js"; +import VCardTime from "./vcard_time.js"; +import Recur from "./recur.js"; +import Period from "./period.js"; +import Duration from "./duration.js"; +import Time from "./time.js"; +import Binary from "./binary.js"; + +/** + * This lets typescript resolve our custom types in the + * generated d.ts files (jsdoc typedefs are converted to typescript types). + * Ignore prevents the typedefs from being documented more than once. + * @ignore + * @typedef {import("./types.js").designSet} designSet + * Imports the 'designSet' type from the "types.js" module + */ + +/** @module ICAL.design */ + +const FROM_ICAL_NEWLINE = /\\\\|\\;|\\,|\\[Nn]/g; +const TO_ICAL_NEWLINE = /\\|;|,|\n/g; +const FROM_VCARD_NEWLINE = /\\\\|\\,|\\[Nn]/g; +const TO_VCARD_NEWLINE = /\\|,|\n/g; + +function createTextType(fromNewline, toNewline) { + let result = { + matches: /.*/, + + fromICAL: function(aValue, structuredEscape) { + return replaceNewline(aValue, fromNewline, structuredEscape); + }, + + toICAL: function(aValue, structuredEscape) { + let regEx = toNewline; + if (structuredEscape) + regEx = new RegExp(regEx.source + '|' + structuredEscape, regEx.flags); + return aValue.replace(regEx, function(str) { + switch (str) { + case "\\": + return "\\\\"; + case ";": + return "\\;"; + case ",": + return "\\,"; + case "\n": + return "\\n"; + /* c8 ignore next 2 */ + default: + return str; + } + }); + } + }; + return result; +} + +// default types used multiple times +const DEFAULT_TYPE_TEXT = { defaultType: "text" }; +const DEFAULT_TYPE_TEXT_MULTI = { defaultType: "text", multiValue: "," }; +const DEFAULT_TYPE_TEXT_STRUCTURED = { defaultType: "text", structuredValue: ";" }; +const DEFAULT_TYPE_INTEGER = { defaultType: "integer" }; +const DEFAULT_TYPE_DATETIME_DATE = { defaultType: "date-time", allowedTypes: ["date-time", "date"] }; +const DEFAULT_TYPE_DATETIME = { defaultType: "date-time" }; +const DEFAULT_TYPE_URI = { defaultType: "uri" }; +const DEFAULT_TYPE_UTCOFFSET = { defaultType: "utc-offset" }; +const DEFAULT_TYPE_RECUR = { defaultType: "recur" }; +const DEFAULT_TYPE_DATE_ANDOR_TIME = { defaultType: "date-and-or-time", allowedTypes: ["date-time", "date", "text"] }; + +function replaceNewlineReplace(string) { + switch (string) { + case "\\\\": + return "\\"; + case "\\;": + return ";"; + case "\\,": + return ","; + case "\\n": + case "\\N": + return "\n"; + /* c8 ignore next 2 */ + default: + return string; + } +} + +function replaceNewline(value, newline, structuredEscape) { + // avoid regex when possible. + if (value.indexOf('\\') === -1) { + return value; + } + if (structuredEscape) + newline = new RegExp(newline.source + '|\\\\' + structuredEscape, newline.flags); + return value.replace(newline, replaceNewlineReplace); +} + +let commonProperties = { + "categories": DEFAULT_TYPE_TEXT_MULTI, + "url": DEFAULT_TYPE_URI, + "version": DEFAULT_TYPE_TEXT, + "uid": DEFAULT_TYPE_TEXT +}; + +let commonValues = { + "boolean": { + values: ["TRUE", "FALSE"], + + fromICAL: function(aValue) { + switch (aValue) { + case 'TRUE': + return true; + case 'FALSE': + return false; + default: + //TODO: parser warning + return false; + } + }, + + toICAL: function(aValue) { + if (aValue) { + return 'TRUE'; + } + return 'FALSE'; + } + + }, + float: { + matches: /^[+-]?\d+\.\d+$/, + + fromICAL: function(aValue) { + let parsed = parseFloat(aValue); + if (isStrictlyNaN(parsed)) { + // TODO: parser warning + return 0.0; + } + return parsed; + }, + + toICAL: function(aValue) { + return String(aValue); + } + }, + integer: { + fromICAL: function(aValue) { + let parsed = parseInt(aValue); + if (isStrictlyNaN(parsed)) { + return 0; + } + return parsed; + }, + + toICAL: function(aValue) { + return String(aValue); + } + }, + "utc-offset": { + toICAL: function(aValue) { + if (aValue.length < 7) { + // no seconds + // -0500 + return aValue.slice(0, 3) + + aValue.slice(4, 6); + } else { + // seconds + // -050000 + return aValue.slice(0, 3) + + aValue.slice(4, 6) + + aValue.slice(7, 9); + } + }, + + fromICAL: function(aValue) { + if (aValue.length < 6) { + // no seconds + // -05:00 + return aValue.slice(0, 3) + ':' + + aValue.slice(3, 5); + } else { + // seconds + // -05:00:00 + return aValue.slice(0, 3) + ':' + + aValue.slice(3, 5) + ':' + + aValue.slice(5, 7); + } + }, + + decorate: function(aValue) { + return UtcOffset.fromString(aValue); + }, + + undecorate: function(aValue) { + return aValue.toString(); + } + } +}; + +let icalParams = { + // Although the syntax is DQUOTE uri DQUOTE, I don't think we should + // enforce anything aside from it being a valid content line. + // + // At least some params require - if multi values are used - DQUOTEs + // for each of its values - e.g. delegated-from="uri1","uri2" + // To indicate this, I introduced the new k/v pair + // multiValueSeparateDQuote: true + // + // "ALTREP": { ... }, + + // CN just wants a param-value + // "CN": { ... } + + "cutype": { + values: ["INDIVIDUAL", "GROUP", "RESOURCE", "ROOM", "UNKNOWN"], + allowXName: true, + allowIanaToken: true + }, + + "delegated-from": { + valueType: "cal-address", + multiValue: ",", + multiValueSeparateDQuote: true + }, + "delegated-to": { + valueType: "cal-address", + multiValue: ",", + multiValueSeparateDQuote: true + }, + // "DIR": { ... }, // See ALTREP + "encoding": { + values: ["8BIT", "BASE64"] + }, + // "FMTTYPE": { ... }, // See ALTREP + "fbtype": { + values: ["FREE", "BUSY", "BUSY-UNAVAILABLE", "BUSY-TENTATIVE"], + allowXName: true, + allowIanaToken: true + }, + // "LANGUAGE": { ... }, // See ALTREP + "member": { + valueType: "cal-address", + multiValue: ",", + multiValueSeparateDQuote: true + }, + "partstat": { + // TODO These values are actually different per-component + values: ["NEEDS-ACTION", "ACCEPTED", "DECLINED", "TENTATIVE", + "DELEGATED", "COMPLETED", "IN-PROCESS"], + allowXName: true, + allowIanaToken: true + }, + "range": { + values: ["THISANDFUTURE"] + }, + "related": { + values: ["START", "END"] + }, + "reltype": { + values: ["PARENT", "CHILD", "SIBLING"], + allowXName: true, + allowIanaToken: true + }, + "role": { + values: ["REQ-PARTICIPANT", "CHAIR", + "OPT-PARTICIPANT", "NON-PARTICIPANT"], + allowXName: true, + allowIanaToken: true + }, + "rsvp": { + values: ["TRUE", "FALSE"] + }, + "sent-by": { + valueType: "cal-address" + }, + "tzid": { + matches: /^\// + }, + "value": { + // since the value here is a 'type' lowercase is used. + values: ["binary", "boolean", "cal-address", "date", "date-time", + "duration", "float", "integer", "period", "recur", "text", + "time", "uri", "utc-offset"], + allowXName: true, + allowIanaToken: true + } +}; + +// When adding a value here, be sure to add it to the parameter types! +const icalValues = extend(commonValues, { + text: createTextType(FROM_ICAL_NEWLINE, TO_ICAL_NEWLINE), + + uri: { + // TODO + /* ... */ + }, + + "binary": { + decorate: function(aString) { + return Binary.fromString(aString); + }, + + undecorate: function(aBinary) { + return aBinary.toString(); + } + }, + "cal-address": { + // needs to be an uri + }, + "date": { + decorate: function(aValue, aProp) { + if (design.strict) { + return Time.fromDateString(aValue, aProp); + } else { + return Time.fromString(aValue, aProp); + } + }, + + /** + * undecorates a time object. + */ + undecorate: function(aValue) { + return aValue.toString(); + }, + + fromICAL: function(aValue) { + // from: 20120901 + // to: 2012-09-01 + if (!design.strict && aValue.length >= 15) { + // This is probably a date-time, e.g. 20120901T130000Z + return icalValues["date-time"].fromICAL(aValue); + } else { + return aValue.slice(0, 4) + '-' + + aValue.slice(4, 6) + '-' + + aValue.slice(6, 8); + } + }, + + toICAL: function(aValue) { + // from: 2012-09-01 + // to: 20120901 + let len = aValue.length; + + if (len == 10) { + return aValue.slice(0, 4) + + aValue.slice(5, 7) + + aValue.slice(8, 10); + } else if (len >= 19) { + return icalValues["date-time"].toICAL(aValue); + } else { + //TODO: serialize warning? + return aValue; + } + + } + }, + "date-time": { + fromICAL: function(aValue) { + // from: 20120901T130000 + // to: 2012-09-01T13:00:00 + if (!design.strict && aValue.length == 8) { + // This is probably a date, e.g. 20120901 + return icalValues.date.fromICAL(aValue); + } else { + let result = aValue.slice(0, 4) + '-' + + aValue.slice(4, 6) + '-' + + aValue.slice(6, 8) + 'T' + + aValue.slice(9, 11) + ':' + + aValue.slice(11, 13) + ':' + + aValue.slice(13, 15); + + if (aValue[15] && aValue[15] === 'Z') { + result += 'Z'; + } + + return result; + } + }, + + toICAL: function(aValue) { + // from: 2012-09-01T13:00:00 + // to: 20120901T130000 + let len = aValue.length; + + if (len == 10 && !design.strict) { + return icalValues.date.toICAL(aValue); + } else if (len >= 19) { + let result = aValue.slice(0, 4) + + aValue.slice(5, 7) + + // grab the (DDTHH) segment + aValue.slice(8, 13) + + // MM + aValue.slice(14, 16) + + // SS + aValue.slice(17, 19); + + if (aValue[19] && aValue[19] === 'Z') { + result += 'Z'; + } + return result; + } else { + // TODO: error + return aValue; + } + }, + + decorate: function(aValue, aProp) { + if (design.strict) { + return Time.fromDateTimeString(aValue, aProp); + } else { + return Time.fromString(aValue, aProp); + } + }, + + undecorate: function(aValue) { + return aValue.toString(); + } + }, + duration: { + decorate: function(aValue) { + return Duration.fromString(aValue); + }, + undecorate: function(aValue) { + return aValue.toString(); + } + }, + period: { + fromICAL: function(string) { + let parts = string.split('/'); + parts[0] = icalValues['date-time'].fromICAL(parts[0]); + + if (!Duration.isValueString(parts[1])) { + parts[1] = icalValues['date-time'].fromICAL(parts[1]); + } + + return parts; + }, + + toICAL: function(parts) { + parts = parts.slice(); + if (!design.strict && parts[0].length == 10) { + parts[0] = icalValues.date.toICAL(parts[0]); + } else { + parts[0] = icalValues['date-time'].toICAL(parts[0]); + } + + if (!Duration.isValueString(parts[1])) { + if (!design.strict && parts[1].length == 10) { + parts[1] = icalValues.date.toICAL(parts[1]); + } else { + parts[1] = icalValues['date-time'].toICAL(parts[1]); + } + } + + return parts.join("/"); + }, + + decorate: function(aValue, aProp) { + return Period.fromJSON(aValue, aProp, !design.strict); + }, + + undecorate: function(aValue) { + return aValue.toJSON(); + } + }, + recur: { + fromICAL: function(string) { + return Recur._stringToData(string, true); + }, + + toICAL: function(data) { + let str = ""; + for (let [k, val] of Object.entries(data)) { + if (k == "until") { + if (val.length > 10) { + val = icalValues['date-time'].toICAL(val); + } else { + val = icalValues.date.toICAL(val); + } + } else if (k == "wkst") { + if (typeof val === 'number') { + val = Recur.numericDayToIcalDay(val); + } + } else if (Array.isArray(val)) { + val = val.join(","); + } + str += k.toUpperCase() + "=" + val + ";"; + } + return str.slice(0, Math.max(0, str.length - 1)); + }, + + decorate: function decorate(aValue) { + return Recur.fromData(aValue); + }, + + undecorate: function(aRecur) { + return aRecur.toJSON(); + } + }, + + time: { + fromICAL: function(aValue) { + // from: MMHHSS(Z)? + // to: HH:MM:SS(Z)? + if (aValue.length < 6) { + // TODO: parser exception? + return aValue; + } + + // HH::MM::SSZ? + let result = aValue.slice(0, 2) + ':' + + aValue.slice(2, 4) + ':' + + aValue.slice(4, 6); + + if (aValue[6] === 'Z') { + result += 'Z'; + } + + return result; + }, + + toICAL: function(aValue) { + // from: HH:MM:SS(Z)? + // to: MMHHSS(Z)? + if (aValue.length < 8) { + //TODO: error + return aValue; + } + + let result = aValue.slice(0, 2) + + aValue.slice(3, 5) + + aValue.slice(6, 8); + + if (aValue[8] === 'Z') { + result += 'Z'; + } + + return result; + } + } +}); + +let icalProperties = extend(commonProperties, { + + "action": DEFAULT_TYPE_TEXT, + "attach": { defaultType: "uri" }, + "attendee": { defaultType: "cal-address" }, + "calscale": DEFAULT_TYPE_TEXT, + "class": DEFAULT_TYPE_TEXT, + "comment": DEFAULT_TYPE_TEXT, + "completed": DEFAULT_TYPE_DATETIME, + "contact": DEFAULT_TYPE_TEXT, + "created": DEFAULT_TYPE_DATETIME, + "description": DEFAULT_TYPE_TEXT, + "dtend": DEFAULT_TYPE_DATETIME_DATE, + "dtstamp": DEFAULT_TYPE_DATETIME, + "dtstart": DEFAULT_TYPE_DATETIME_DATE, + "due": DEFAULT_TYPE_DATETIME_DATE, + "duration": { defaultType: "duration" }, + "exdate": { + defaultType: "date-time", + allowedTypes: ["date-time", "date"], + multiValue: ',' + }, + "exrule": DEFAULT_TYPE_RECUR, + "freebusy": { defaultType: "period", multiValue: "," }, + "geo": { defaultType: "float", structuredValue: ";" }, + "last-modified": DEFAULT_TYPE_DATETIME, + "location": DEFAULT_TYPE_TEXT, + "method": DEFAULT_TYPE_TEXT, + "organizer": { defaultType: "cal-address" }, + "percent-complete": DEFAULT_TYPE_INTEGER, + "priority": DEFAULT_TYPE_INTEGER, + "prodid": DEFAULT_TYPE_TEXT, + "related-to": DEFAULT_TYPE_TEXT, + "repeat": DEFAULT_TYPE_INTEGER, + "rdate": { + defaultType: "date-time", + allowedTypes: ["date-time", "date", "period"], + multiValue: ',', + detectType: function(string) { + if (string.indexOf('/') !== -1) { + return 'period'; + } + return (string.indexOf('T') === -1) ? 'date' : 'date-time'; + } + }, + "recurrence-id": DEFAULT_TYPE_DATETIME_DATE, + "resources": DEFAULT_TYPE_TEXT_MULTI, + "request-status": DEFAULT_TYPE_TEXT_STRUCTURED, + "rrule": DEFAULT_TYPE_RECUR, + "sequence": DEFAULT_TYPE_INTEGER, + "status": DEFAULT_TYPE_TEXT, + "summary": DEFAULT_TYPE_TEXT, + "transp": DEFAULT_TYPE_TEXT, + "trigger": { defaultType: "duration", allowedTypes: ["duration", "date-time"] }, + "tzoffsetfrom": DEFAULT_TYPE_UTCOFFSET, + "tzoffsetto": DEFAULT_TYPE_UTCOFFSET, + "tzurl": DEFAULT_TYPE_URI, + "tzid": DEFAULT_TYPE_TEXT, + "tzname": DEFAULT_TYPE_TEXT +}); + +// When adding a value here, be sure to add it to the parameter types! +const vcardValues = extend(commonValues, { + text: createTextType(FROM_VCARD_NEWLINE, TO_VCARD_NEWLINE), + uri: createTextType(FROM_VCARD_NEWLINE, TO_VCARD_NEWLINE), + + date: { + decorate: function(aValue) { + return VCardTime.fromDateAndOrTimeString(aValue, "date"); + }, + undecorate: function(aValue) { + return aValue.toString(); + }, + fromICAL: function(aValue) { + if (aValue.length == 8) { + return icalValues.date.fromICAL(aValue); + } else if (aValue[0] == '-' && aValue.length == 6) { + return aValue.slice(0, 4) + '-' + aValue.slice(4); + } else { + return aValue; + } + }, + toICAL: function(aValue) { + if (aValue.length == 10) { + return icalValues.date.toICAL(aValue); + } else if (aValue[0] == '-' && aValue.length == 7) { + return aValue.slice(0, 4) + aValue.slice(5); + } else { + return aValue; + } + } + }, + + time: { + decorate: function(aValue) { + return VCardTime.fromDateAndOrTimeString("T" + aValue, "time"); + }, + undecorate: function(aValue) { + return aValue.toString(); + }, + fromICAL: function(aValue) { + let splitzone = vcardValues.time._splitZone(aValue, true); + let zone = splitzone[0], value = splitzone[1]; + + //console.log("SPLIT: ",splitzone); + + if (value.length == 6) { + value = value.slice(0, 2) + ':' + + value.slice(2, 4) + ':' + + value.slice(4, 6); + } else if (value.length == 4 && value[0] != '-') { + value = value.slice(0, 2) + ':' + value.slice(2, 4); + } else if (value.length == 5) { + value = value.slice(0, 3) + ':' + value.slice(3, 5); + } + + if (zone.length == 5 && (zone[0] == '-' || zone[0] == '+')) { + zone = zone.slice(0, 3) + ':' + zone.slice(3); + } + + return value + zone; + }, + + toICAL: function(aValue) { + let splitzone = vcardValues.time._splitZone(aValue); + let zone = splitzone[0], value = splitzone[1]; + + if (value.length == 8) { + value = value.slice(0, 2) + + value.slice(3, 5) + + value.slice(6, 8); + } else if (value.length == 5 && value[0] != '-') { + value = value.slice(0, 2) + value.slice(3, 5); + } else if (value.length == 6) { + value = value.slice(0, 3) + value.slice(4, 6); + } + + if (zone.length == 6 && (zone[0] == '-' || zone[0] == '+')) { + zone = zone.slice(0, 3) + zone.slice(4); + } + + return value + zone; + }, + + _splitZone: function(aValue, isFromIcal) { + let lastChar = aValue.length - 1; + let signChar = aValue.length - (isFromIcal ? 5 : 6); + let sign = aValue[signChar]; + let zone, value; + + if (aValue[lastChar] == 'Z') { + zone = aValue[lastChar]; + value = aValue.slice(0, Math.max(0, lastChar)); + } else if (aValue.length > 6 && (sign == '-' || sign == '+')) { + zone = aValue.slice(signChar); + value = aValue.slice(0, Math.max(0, signChar)); + } else { + zone = ""; + value = aValue; + } + + return [zone, value]; + } + }, + + "date-time": { + decorate: function(aValue) { + return VCardTime.fromDateAndOrTimeString(aValue, "date-time"); + }, + + undecorate: function(aValue) { + return aValue.toString(); + }, + + fromICAL: function(aValue) { + return vcardValues['date-and-or-time'].fromICAL(aValue); + }, + + toICAL: function(aValue) { + return vcardValues['date-and-or-time'].toICAL(aValue); + } + }, + + "date-and-or-time": { + decorate: function(aValue) { + return VCardTime.fromDateAndOrTimeString(aValue, "date-and-or-time"); + }, + + undecorate: function(aValue) { + return aValue.toString(); + }, + + fromICAL: function(aValue) { + let parts = aValue.split('T'); + return (parts[0] ? vcardValues.date.fromICAL(parts[0]) : '') + + (parts[1] ? 'T' + vcardValues.time.fromICAL(parts[1]) : ''); + }, + + toICAL: function(aValue) { + let parts = aValue.split('T'); + return vcardValues.date.toICAL(parts[0]) + + (parts[1] ? 'T' + vcardValues.time.toICAL(parts[1]) : ''); + + } + }, + timestamp: icalValues['date-time'], + "language-tag": { + matches: /^[a-zA-Z0-9-]+$/ // Could go with a more strict regex here + }, + "phone-number": { + fromICAL: function(aValue) { + return Array.from(aValue).filter(function(c) { + return c === '\\' ? undefined : c; + }).join(''); + }, + toICAL: function(aValue) { + return Array.from(aValue).map(function(c) { + return c === ',' || c === ";" ? '\\' + c : c; + }).join(''); + } + } +}); + +let vcardParams = { + "type": { + valueType: "text", + multiValue: "," + }, + "value": { + // since the value here is a 'type' lowercase is used. + values: ["text", "uri", "date", "time", "date-time", "date-and-or-time", + "timestamp", "boolean", "integer", "float", "utc-offset", + "language-tag"], + allowXName: true, + allowIanaToken: true + } +}; + +let vcardProperties = extend(commonProperties, { + "adr": { defaultType: "text", structuredValue: ";", multiValue: "," }, + "anniversary": DEFAULT_TYPE_DATE_ANDOR_TIME, + "bday": DEFAULT_TYPE_DATE_ANDOR_TIME, + "caladruri": DEFAULT_TYPE_URI, + "caluri": DEFAULT_TYPE_URI, + "clientpidmap": DEFAULT_TYPE_TEXT_STRUCTURED, + "email": DEFAULT_TYPE_TEXT, + "fburl": DEFAULT_TYPE_URI, + "fn": DEFAULT_TYPE_TEXT, + "gender": DEFAULT_TYPE_TEXT_STRUCTURED, + "geo": DEFAULT_TYPE_URI, + "impp": DEFAULT_TYPE_URI, + "key": DEFAULT_TYPE_URI, + "kind": DEFAULT_TYPE_TEXT, + "lang": { defaultType: "language-tag" }, + "logo": DEFAULT_TYPE_URI, + "member": DEFAULT_TYPE_URI, + "n": { defaultType: "text", structuredValue: ";", multiValue: "," }, + "nickname": DEFAULT_TYPE_TEXT_MULTI, + "note": DEFAULT_TYPE_TEXT, + "org": { defaultType: "text", structuredValue: ";" }, + "photo": DEFAULT_TYPE_URI, + "related": DEFAULT_TYPE_URI, + "rev": { defaultType: "timestamp" }, + "role": DEFAULT_TYPE_TEXT, + "sound": DEFAULT_TYPE_URI, + "source": DEFAULT_TYPE_URI, + "tel": { defaultType: "uri", allowedTypes: ["uri", "text"] }, + "title": DEFAULT_TYPE_TEXT, + "tz": { defaultType: "text", allowedTypes: ["text", "utc-offset", "uri"] }, + "xml": DEFAULT_TYPE_TEXT +}); + +let vcard3Values = extend(commonValues, { + binary: icalValues.binary, + date: vcardValues.date, + "date-time": vcardValues["date-time"], + "phone-number": vcardValues["phone-number"], + uri: icalValues.uri, + text: icalValues.text, + time: icalValues.time, + vcard: icalValues.text, + "utc-offset": { + toICAL: function(aValue) { + return aValue.slice(0, 7); + }, + + fromICAL: function(aValue) { + return aValue.slice(0, 7); + }, + + decorate: function(aValue) { + return UtcOffset.fromString(aValue); + }, + + undecorate: function(aValue) { + return aValue.toString(); + } + } +}); + +let vcard3Params = { + "type": { + valueType: "text", + multiValue: "," + }, + "value": { + // since the value here is a 'type' lowercase is used. + values: ["text", "uri", "date", "date-time", "phone-number", "time", + "boolean", "integer", "float", "utc-offset", "vcard", "binary"], + allowXName: true, + allowIanaToken: true + } +}; + +let vcard3Properties = extend(commonProperties, { + fn: DEFAULT_TYPE_TEXT, + n: { defaultType: "text", structuredValue: ";", multiValue: "," }, + nickname: DEFAULT_TYPE_TEXT_MULTI, + photo: { defaultType: "binary", allowedTypes: ["binary", "uri"] }, + bday: { + defaultType: "date-time", + allowedTypes: ["date-time", "date"], + detectType: function(string) { + return (string.indexOf('T') === -1) ? 'date' : 'date-time'; + } + }, + + adr: { defaultType: "text", structuredValue: ";", multiValue: "," }, + label: DEFAULT_TYPE_TEXT, + + tel: { defaultType: "phone-number" }, + email: DEFAULT_TYPE_TEXT, + mailer: DEFAULT_TYPE_TEXT, + + tz: { defaultType: "utc-offset", allowedTypes: ["utc-offset", "text"] }, + geo: { defaultType: "float", structuredValue: ";" }, + + title: DEFAULT_TYPE_TEXT, + role: DEFAULT_TYPE_TEXT, + logo: { defaultType: "binary", allowedTypes: ["binary", "uri"] }, + agent: { defaultType: "vcard", allowedTypes: ["vcard", "text", "uri"] }, + org: DEFAULT_TYPE_TEXT_STRUCTURED, + + note: DEFAULT_TYPE_TEXT_MULTI, + prodid: DEFAULT_TYPE_TEXT, + rev: { + defaultType: "date-time", + allowedTypes: ["date-time", "date"], + detectType: function(string) { + return (string.indexOf('T') === -1) ? 'date' : 'date-time'; + } + }, + "sort-string": DEFAULT_TYPE_TEXT, + sound: { defaultType: "binary", allowedTypes: ["binary", "uri"] }, + + class: DEFAULT_TYPE_TEXT, + key: { defaultType: "binary", allowedTypes: ["binary", "text"] } +}); + +/** + * iCalendar design set + * @type {designSet} + */ +let icalSet = { + value: icalValues, + param: icalParams, + property: icalProperties, + propertyGroups: false +}; + +/** + * vCard 4.0 design set + * @type {designSet} + */ +let vcardSet = { + value: vcardValues, + param: vcardParams, + property: vcardProperties, + propertyGroups: true +}; + +/** + * vCard 3.0 design set + * @type {designSet} + */ +let vcard3Set = { + value: vcard3Values, + param: vcard3Params, + property: vcard3Properties, + propertyGroups: true +}; + +/** + * The design data, used by the parser to determine types for properties and + * other metadata needed to produce correct jCard/jCal data. + * + * @alias ICAL.design + * @exports module:ICAL.design + */ +const design = { + /** + * Can be set to false to make the parser more lenient. + */ + strict: true, + + /** + * The default set for new properties and components if none is specified. + * @type {designSet} + */ + defaultSet: icalSet, + + /** + * The default type for unknown properties + * @type {String} + */ + defaultType: 'unknown', + + /** + * Holds the design set for known top-level components + * + * @type {Object} + * @property {designSet} vcard vCard VCARD + * @property {designSet} vevent iCalendar VEVENT + * @property {designSet} vtodo iCalendar VTODO + * @property {designSet} vjournal iCalendar VJOURNAL + * @property {designSet} valarm iCalendar VALARM + * @property {designSet} vtimezone iCalendar VTIMEZONE + * @property {designSet} daylight iCalendar DAYLIGHT + * @property {designSet} standard iCalendar STANDARD + * + * @example + * let propertyName = 'fn'; + * let componentDesign = ICAL.design.components.vcard; + * let propertyDetails = componentDesign.property[propertyName]; + * if (propertyDetails.defaultType == 'text') { + * // Yep, sure is... + * } + */ + components: { + vcard: vcardSet, + vcard3: vcard3Set, + vevent: icalSet, + vtodo: icalSet, + vjournal: icalSet, + valarm: icalSet, + vtimezone: icalSet, + daylight: icalSet, + standard: icalSet + }, + + + /** + * The design set for iCalendar (rfc5545/rfc7265) components. + * @type {designSet} + */ + icalendar: icalSet, + + /** + * The design set for vCard (rfc6350/rfc7095) components. + * @type {designSet} + */ + vcard: vcardSet, + + /** + * The design set for vCard (rfc2425/rfc2426/rfc7095) components. + * @type {designSet} + */ + vcard3: vcard3Set, + + /** + * Gets the design set for the given component name. + * + * @param {String} componentName The name of the component + * @return {designSet} The design set for the component + */ + getDesignSet: function(componentName) { + let isInDesign = componentName && componentName in design.components; + return isInDesign ? design.components[componentName] : design.defaultSet; + } +}; +export default design; diff --git a/lib/ical/duration.js b/lib/ical/duration.js new file mode 100644 index 0000000..232f759 --- /dev/null +++ b/lib/ical/duration.js @@ -0,0 +1,354 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */ + +import { isStrictlyNaN, trunc } from "./helpers.js"; + +const DURATION_LETTERS = /([PDWHMTS]{1,1})/; +const DATA_PROPS_TO_COPY = ["weeks", "days", "hours", "minutes", "seconds", "isNegative"]; + +/** + * This class represents the "duration" value type, with various calculation + * and manipulation methods. + * + * @memberof ICAL + */ +class Duration { + /** + * Returns a new ICAL.Duration instance from the passed seconds value. + * + * @param {Number} aSeconds The seconds to create the instance from + * @return {Duration} The newly created duration instance + */ + static fromSeconds(aSeconds) { + return (new Duration()).fromSeconds(aSeconds); + } + + /** + * Checks if the given string is an iCalendar duration value. + * + * @param {String} value The raw ical value + * @return {Boolean} True, if the given value is of the + * duration ical type + */ + static isValueString(string) { + return (string[0] === 'P' || string[1] === 'P'); + } + + /** + * Creates a new {@link ICAL.Duration} instance from the passed string. + * + * @param {String} aStr The string to parse + * @return {Duration} The created duration instance + */ + static fromString(aStr) { + let pos = 0; + let dict = Object.create(null); + let chunks = 0; + + while ((pos = aStr.search(DURATION_LETTERS)) !== -1) { + let type = aStr[pos]; + let numeric = aStr.slice(0, Math.max(0, pos)); + aStr = aStr.slice(pos + 1); + + chunks += parseDurationChunk(type, numeric, dict); + } + + if (chunks < 2) { + // There must be at least a chunk with "P" and some unit chunk + throw new Error( + 'invalid duration value: Not enough duration components in "' + aStr + '"' + ); + } + + return new Duration(dict); + } + + /** + * Creates a new ICAL.Duration instance from the given data object. + * + * @param {Object} aData An object with members of the duration + * @param {Number=} aData.weeks Duration in weeks + * @param {Number=} aData.days Duration in days + * @param {Number=} aData.hours Duration in hours + * @param {Number=} aData.minutes Duration in minutes + * @param {Number=} aData.seconds Duration in seconds + * @param {Boolean=} aData.isNegative If true, the duration is negative + * @return {Duration} The createad duration instance + */ + static fromData(aData) { + return new Duration(aData); + } + + /** + * Creates a new ICAL.Duration instance. + * + * @param {Object} data An object with members of the duration + * @param {Number=} data.weeks Duration in weeks + * @param {Number=} data.days Duration in days + * @param {Number=} data.hours Duration in hours + * @param {Number=} data.minutes Duration in minutes + * @param {Number=} data.seconds Duration in seconds + * @param {Boolean=} data.isNegative If true, the duration is negative + */ + constructor(data) { + this.wrappedJSObject = this; + this.fromData(data); + } + + /** + * The weeks in this duration + * @type {Number} + * @default 0 + */ + weeks = 0; + + /** + * The days in this duration + * @type {Number} + * @default 0 + */ + days = 0; + + /** + * The days in this duration + * @type {Number} + * @default 0 + */ + hours = 0; + + /** + * The minutes in this duration + * @type {Number} + * @default 0 + */ + minutes = 0; + + /** + * The seconds in this duration + * @type {Number} + * @default 0 + */ + seconds = 0; + + /** + * The seconds in this duration + * @type {Boolean} + * @default false + */ + isNegative = false; + + /** + * The class identifier. + * @constant + * @type {String} + * @default "icalduration" + */ + icalclass = "icalduration"; + + /** + * The type name, to be used in the jCal object. + * @constant + * @type {String} + * @default "duration" + */ + icaltype = "duration"; + + /** + * Returns a clone of the duration object. + * + * @return {Duration} The cloned object + */ + clone() { + return Duration.fromData(this); + } + + /** + * The duration value expressed as a number of seconds. + * + * @return {Number} The duration value in seconds + */ + toSeconds() { + let seconds = this.seconds + 60 * this.minutes + 3600 * this.hours + + 86400 * this.days + 7 * 86400 * this.weeks; + return (this.isNegative ? -seconds : seconds); + } + + /** + * Reads the passed seconds value into this duration object. Afterwards, + * members like {@link ICAL.Duration#days days} and {@link ICAL.Duration#weeks weeks} will be set up + * accordingly. + * + * @param {Number} aSeconds The duration value in seconds + * @return {Duration} Returns this instance + */ + fromSeconds(aSeconds) { + let secs = Math.abs(aSeconds); + + this.isNegative = (aSeconds < 0); + this.days = trunc(secs / 86400); + + // If we have a flat number of weeks, use them. + if (this.days % 7 == 0) { + this.weeks = this.days / 7; + this.days = 0; + } else { + this.weeks = 0; + } + + secs -= (this.days + 7 * this.weeks) * 86400; + + this.hours = trunc(secs / 3600); + secs -= this.hours * 3600; + + this.minutes = trunc(secs / 60); + secs -= this.minutes * 60; + + this.seconds = secs; + return this; + } + + /** + * Sets up the current instance using members from the passed data object. + * + * @param {Object} aData An object with members of the duration + * @param {Number=} aData.weeks Duration in weeks + * @param {Number=} aData.days Duration in days + * @param {Number=} aData.hours Duration in hours + * @param {Number=} aData.minutes Duration in minutes + * @param {Number=} aData.seconds Duration in seconds + * @param {Boolean=} aData.isNegative If true, the duration is negative + */ + fromData(aData) { + for (let prop of DATA_PROPS_TO_COPY) { + if (aData && prop in aData) { + this[prop] = aData[prop]; + } else { + this[prop] = 0; + } + } + } + + /** + * Resets the duration instance to the default values, i.e. PT0S + */ + reset() { + this.isNegative = false; + this.weeks = 0; + this.days = 0; + this.hours = 0; + this.minutes = 0; + this.seconds = 0; + } + + /** + * Compares the duration instance with another one. + * + * @param {Duration} aOther The instance to compare with + * @return {Number} -1, 0 or 1 for less/equal/greater + */ + compare(aOther) { + let thisSeconds = this.toSeconds(); + let otherSeconds = aOther.toSeconds(); + return (thisSeconds > otherSeconds) - (thisSeconds < otherSeconds); + } + + /** + * Normalizes the duration instance. For example, a duration with a value + * of 61 seconds will be normalized to 1 minute and 1 second. + */ + normalize() { + this.fromSeconds(this.toSeconds()); + } + + /** + * The string representation of this duration. + * @return {String} + */ + toString() { + if (this.toSeconds() == 0) { + return "PT0S"; + } else { + let str = ""; + if (this.isNegative) str += "-"; + str += "P"; + if (this.weeks) str += this.weeks + "W"; + if (this.days) str += this.days + "D"; + + if (this.hours || this.minutes || this.seconds) { + str += "T"; + if (this.hours) str += this.hours + "H"; + if (this.minutes) str += this.minutes + "M"; + if (this.seconds) str += this.seconds + "S"; + } + return str; + } + } + + /** + * The iCalendar string representation of this duration. + * @return {String} + */ + toICALString() { + return this.toString(); + } +} +export default Duration; + +/** + * Internal helper function to handle a chunk of a duration. + * + * @private + * @param {String} letter type of duration chunk + * @param {String} number numeric value or -/+ + * @param {Object} dict target to assign values to + */ +function parseDurationChunk(letter, number, object) { + let type; + switch (letter) { + case 'P': + if (number && number === '-') { + object.isNegative = true; + } else { + object.isNegative = false; + } + // period + break; + case 'D': + type = 'days'; + break; + case 'W': + type = 'weeks'; + break; + case 'H': + type = 'hours'; + break; + case 'M': + type = 'minutes'; + break; + case 'S': + type = 'seconds'; + break; + default: + // Not a valid chunk + return 0; + } + + if (type) { + if (!number && number !== 0) { + throw new Error( + 'invalid duration value: Missing number before "' + letter + '"' + ); + } + let num = parseInt(number, 10); + if (isStrictlyNaN(num)) { + throw new Error( + 'invalid duration value: Invalid number "' + number + '" before "' + letter + '"' + ); + } + object[type] = num; + } + + return 1; +} diff --git a/lib/ical/event.js b/lib/ical/event.js new file mode 100644 index 0000000..f8268c0 --- /dev/null +++ b/lib/ical/event.js @@ -0,0 +1,569 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */ + +import { binsearchInsert } from "./helpers.js"; +import Component from "./component.js"; +import Property from "./property.js"; +import Timezone from "./timezone.js"; +// needed for typescript type resolution +// eslint-disable-next-line no-unused-vars +import Time from "./time.js"; +// eslint-disable-next-line no-unused-vars +import Duration from "./duration.js"; +import RecurExpansion from "./recur_expansion.js"; + +/** + * This lets typescript resolve our custom types in the + * generated d.ts files (jsdoc typedefs are converted to typescript types). + * Ignore prevents the typedefs from being documented more than once. + * @ignore + * @typedef {import("./types.js").frequencyValues} frequencyValues + * Imports the 'frequencyValues' type from the "types.js" module + * @typedef {import("./types.js").occurrenceDetails} occurrenceDetails + * Imports the 'occurrenceDetails' type from the "types.js" module + */ + +/** + * ICAL.js is organized into multiple layers. The bottom layer is a raw jCal + * object, followed by the component/property layer. The highest level is the + * event representation, which this class is part of. See the + * {@tutorial layers} guide for more details. + * + * @memberof ICAL + */ +class Event { + /** + * Creates a new ICAL.Event instance. + * + * @param {Component=} component The ICAL.Component to base this event on + * @param {Object} [options] Options for this event + * @param {Boolean=} options.strictExceptions When true, will verify exceptions are related by + * their UUID + * @param {Array=} options.exceptions + * Exceptions to this event, either as components or events. If not + * specified exceptions will automatically be set in relation of + * component's parent + */ + constructor(component, options) { + if (!(component instanceof Component)) { + options = component; + component = null; + } + + if (component) { + this.component = component; + } else { + this.component = new Component('vevent'); + } + + this._rangeExceptionCache = Object.create(null); + this.exceptions = Object.create(null); + this.rangeExceptions = []; + + if (options && options.strictExceptions) { + this.strictExceptions = options.strictExceptions; + } + + if (options && options.exceptions) { + options.exceptions.forEach(this.relateException, this); + } else if (this.component.parent && !this.isRecurrenceException()) { + this.component.parent.getAllSubcomponents('vevent').forEach(function(event) { + if (event.hasProperty('recurrence-id')) { + this.relateException(event); + } + }, this); + } + } + + + static THISANDFUTURE = 'THISANDFUTURE'; + + /** + * List of related event exceptions. + * + * @type {Event[]} + */ + exceptions = null; + + /** + * When true, will verify exceptions are related by their UUID. + * + * @type {Boolean} + */ + strictExceptions = false; + + /** + * Relates a given event exception to this object. If the given component + * does not share the UID of this event it cannot be related and will throw + * an exception. + * + * If this component is an exception it cannot have other exceptions + * related to it. + * + * @param {Component|Event} obj Component or event + */ + relateException(obj) { + if (this.isRecurrenceException()) { + throw new Error('cannot relate exception to exceptions'); + } + + if (obj instanceof Component) { + obj = new Event(obj); + } + + if (this.strictExceptions && obj.uid !== this.uid) { + throw new Error('attempted to relate unrelated exception'); + } + + let id = obj.recurrenceId.toString(); + + // we don't sort or manage exceptions directly + // here the recurrence expander handles that. + this.exceptions[id] = obj; + + // index RANGE=THISANDFUTURE exceptions so we can + // look them up later in getOccurrenceDetails. + if (obj.modifiesFuture()) { + let item = [ + obj.recurrenceId.toUnixTime(), id + ]; + + // we keep them sorted so we can find the nearest + // value later on... + let idx = binsearchInsert( + this.rangeExceptions, + item, + compareRangeException + ); + + this.rangeExceptions.splice(idx, 0, item); + } + } + + /** + * Checks if this record is an exception and has the RANGE=THISANDFUTURE + * value. + * + * @return {Boolean} True, when exception is within range + */ + modifiesFuture() { + if (!this.component.hasProperty('recurrence-id')) { + return false; + } + + let range = this.component.getFirstProperty('recurrence-id').getParameter('range'); + return range === Event.THISANDFUTURE; + } + + /** + * Finds the range exception nearest to the given date. + * + * @param {Time} time usually an occurrence time of an event + * @return {?Event} the related event/exception or null + */ + findRangeException(time) { + if (!this.rangeExceptions.length) { + return null; + } + + let utc = time.toUnixTime(); + let idx = binsearchInsert( + this.rangeExceptions, + [utc], + compareRangeException + ); + + idx -= 1; + + // occurs before + if (idx < 0) { + return null; + } + + let rangeItem = this.rangeExceptions[idx]; + + /* c8 ignore next 4 */ + if (utc < rangeItem[0]) { + // sanity check only + return null; + } + + return rangeItem[1]; + } + + /** + * Returns the occurrence details based on its start time. If the + * occurrence has an exception will return the details for that exception. + * + * NOTE: this method is intend to be used in conjunction + * with the {@link ICAL.Event#iterator iterator} method. + * + * @param {Time} occurrence time occurrence + * @return {occurrenceDetails} Information about the occurrence + */ + getOccurrenceDetails(occurrence) { + let id = occurrence.toString(); + let utcId = occurrence.convertToZone(Timezone.utcTimezone).toString(); + let item; + let result = { + //XXX: Clone? + recurrenceId: occurrence + }; + + if (id in this.exceptions) { + item = result.item = this.exceptions[id]; + result.startDate = item.startDate; + result.endDate = item.endDate; + result.item = item; + } else if (utcId in this.exceptions) { + item = this.exceptions[utcId]; + result.startDate = item.startDate; + result.endDate = item.endDate; + result.item = item; + } else { + // range exceptions (RANGE=THISANDFUTURE) have a + // lower priority then direct exceptions but + // must be accounted for first. Their item is + // always the first exception with the range prop. + let rangeExceptionId = this.findRangeException( + occurrence + ); + let end; + + if (rangeExceptionId) { + let exception = this.exceptions[rangeExceptionId]; + + // range exception must modify standard time + // by the difference (if any) in start/end times. + result.item = exception; + + let startDiff = this._rangeExceptionCache[rangeExceptionId]; + + if (!startDiff) { + let original = exception.recurrenceId.clone(); + let newStart = exception.startDate.clone(); + + // zones must be same otherwise subtract may be incorrect. + original.zone = newStart.zone; + startDiff = newStart.subtractDate(original); + + this._rangeExceptionCache[rangeExceptionId] = startDiff; + } + + let start = occurrence.clone(); + start.zone = exception.startDate.zone; + start.addDuration(startDiff); + + end = start.clone(); + end.addDuration(exception.duration); + + result.startDate = start; + result.endDate = end; + } else { + // no range exception standard expansion + end = occurrence.clone(); + end.addDuration(this.duration); + + result.endDate = end; + result.startDate = occurrence; + result.item = this; + } + } + + return result; + } + + /** + * Builds a recur expansion instance for a specific point in time (defaults + * to startDate). + * + * @param {Time=} startTime Starting point for expansion + * @return {RecurExpansion} Expansion object + */ + iterator(startTime) { + return new RecurExpansion({ + component: this.component, + dtstart: startTime || this.startDate + }); + } + + /** + * Checks if the event is recurring + * + * @return {Boolean} True, if event is recurring + */ + isRecurring() { + let comp = this.component; + return comp.hasProperty('rrule') || comp.hasProperty('rdate'); + } + + /** + * Checks if the event describes a recurrence exception. See + * {@tutorial terminology} for details. + * + * @return {Boolean} True, if the event describes a recurrence exception + */ + isRecurrenceException() { + return this.component.hasProperty('recurrence-id'); + } + + /** + * Returns the types of recurrences this event may have. + * + * Returned as an object with the following possible keys: + * + * - YEARLY + * - MONTHLY + * - WEEKLY + * - DAILY + * - MINUTELY + * - SECONDLY + * + * @return {Object.} + * Object of recurrence flags + */ + getRecurrenceTypes() { + let rules = this.component.getAllProperties('rrule'); + let i = 0; + let len = rules.length; + let result = Object.create(null); + + for (; i < len; i++) { + let value = rules[i].getFirstValue(); + result[value.freq] = true; + } + + return result; + } + + /** + * The uid of this event + * @type {String} + */ + get uid() { + return this._firstProp('uid'); + } + + set uid(value) { + this._setProp('uid', value); + } + + /** + * The start date + * @type {Time} + */ + get startDate() { + return this._firstProp('dtstart'); + } + + set startDate(value) { + this._setTime('dtstart', value); + } + + /** + * The end date. This can be the result directly from the property, or the + * end date calculated from start date and duration. Setting the property + * will remove any duration properties. + * @type {Time} + */ + get endDate() { + let endDate = this._firstProp('dtend'); + if (!endDate) { + let duration = this._firstProp('duration'); + endDate = this.startDate.clone(); + if (duration) { + endDate.addDuration(duration); + } else if (endDate.isDate) { + endDate.day += 1; + } + } + return endDate; + } + + set endDate(value) { + if (this.component.hasProperty('duration')) { + this.component.removeProperty('duration'); + } + this._setTime('dtend', value); + } + + /** + * The duration. This can be the result directly from the property, or the + * duration calculated from start date and end date. Setting the property + * will remove any `dtend` properties. + * @type {Duration} + */ + get duration() { + let duration = this._firstProp('duration'); + if (!duration) { + return this.endDate.subtractDateTz(this.startDate); + } + return duration; + } + + set duration(value) { + if (this.component.hasProperty('dtend')) { + this.component.removeProperty('dtend'); + } + + this._setProp('duration', value); + } + + /** + * The location of the event. + * @type {String} + */ + get location() { + return this._firstProp('location'); + } + + set location(value) { + this._setProp('location', value); + } + + /** + * The attendees in the event + * @type {Property[]} + */ + get attendees() { + //XXX: This is way lame we should have a better + // data structure for this later. + return this.component.getAllProperties('attendee'); + } + + /** + * The event summary + * @type {String} + */ + get summary() { + return this._firstProp('summary'); + } + + set summary(value) { + this._setProp('summary', value); + } + + /** + * The event description. + * @type {String} + */ + get description() { + return this._firstProp('description'); + } + + set description(value) { + this._setProp('description', value); + } + + /** + * The event color from [rfc7986](https://datatracker.ietf.org/doc/html/rfc7986) + * @type {String} + */ + get color() { + return this._firstProp('color'); + } + + set color(value) { + this._setProp('color', value); + } + + /** + * The organizer value as an uri. In most cases this is a mailto: uri, but + * it can also be something else, like urn:uuid:... + * @type {String} + */ + get organizer() { + return this._firstProp('organizer'); + } + + set organizer(value) { + this._setProp('organizer', value); + } + + /** + * The sequence value for this event. Used for scheduling + * see {@tutorial terminology}. + * @type {Number} + */ + get sequence() { + return this._firstProp('sequence'); + } + + set sequence(value) { + this._setProp('sequence', value); + } + + /** + * The recurrence id for this event. See {@tutorial terminology} for details. + * @type {Time} + */ + get recurrenceId() { + return this._firstProp('recurrence-id'); + } + + set recurrenceId(value) { + this._setTime('recurrence-id', value); + } + + /** + * Set/update a time property's value. + * This will also update the TZID of the property. + * + * TODO: this method handles the case where we are switching + * from a known timezone to an implied timezone (one without TZID). + * This does _not_ handle the case of moving between a known + * (by TimezoneService) timezone to an unknown timezone... + * + * We will not add/remove/update the VTIMEZONE subcomponents + * leading to invalid ICAL data... + * @private + * @param {String} propName The property name + * @param {Time} time The time to set + */ + _setTime(propName, time) { + let prop = this.component.getFirstProperty(propName); + + if (!prop) { + prop = new Property(propName); + this.component.addProperty(prop); + } + + // utc and local don't get a tzid + if ( + time.zone === Timezone.localTimezone || + time.zone === Timezone.utcTimezone + ) { + // remove the tzid + prop.removeParameter('tzid'); + } else { + prop.setParameter('tzid', time.zone.tzid); + } + + prop.setValue(time); + } + + _setProp(name, value) { + this.component.updatePropertyWithValue(name, value); + } + + _firstProp(name) { + return this.component.getFirstPropertyValue(name); + } + + /** + * The string representation of this event. + * @return {String} + */ + toString() { + return this.component.toString(); + } +} +export default Event; + +function compareRangeException(a, b) { + if (a[0] > b[0]) return 1; + if (b[0] > a[0]) return -1; + return 0; +} diff --git a/lib/ical/helpers.js b/lib/ical/helpers.js new file mode 100644 index 0000000..ba3cf6c --- /dev/null +++ b/lib/ical/helpers.js @@ -0,0 +1,317 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */ + +import TimezoneService from "./timezone_service.js"; +// needed for typescript type resolution +// eslint-disable-next-line no-unused-vars +import Component from "./component.js"; +import ICALmodule from "./module.js"; + +/** + * Helper functions used in various places within ical.js + * @module ICAL.helpers + */ + +/** + * Compiles a list of all referenced TZIDs in all subcomponents and + * removes any extra VTIMEZONE subcomponents. In addition, if any TZIDs + * are referenced by a component, but a VTIMEZONE does not exist, + * an attempt will be made to generate a VTIMEZONE using ICAL.TimezoneService. + * + * @param {Component} vcal The top-level VCALENDAR component. + * @return {Component} The ICAL.Component that was passed in. + */ +export function updateTimezones(vcal) { + let allsubs, properties, vtimezones, reqTzid, i; + + if (!vcal || vcal.name !== "vcalendar") { + //not a top-level vcalendar component + return vcal; + } + + //Store vtimezone subcomponents in an object reference by tzid. + //Store properties from everything else in another array + allsubs = vcal.getAllSubcomponents(); + properties = []; + vtimezones = {}; + for (i = 0; i < allsubs.length; i++) { + if (allsubs[i].name === "vtimezone") { + let tzid = allsubs[i].getFirstProperty("tzid").getFirstValue(); + vtimezones[tzid] = allsubs[i]; + } else { + properties = properties.concat(allsubs[i].getAllProperties()); + } + } + + //create an object with one entry for each required tz + reqTzid = {}; + for (i = 0; i < properties.length; i++) { + let tzid = properties[i].getParameter("tzid"); + if (tzid) { + reqTzid[tzid] = true; + } + } + + //delete any vtimezones that are not on the reqTzid list. + for (let [tzid, comp] of Object.entries(vtimezones)) { + if (!reqTzid[tzid]) { + vcal.removeSubcomponent(comp); + } + } + + //create any missing, but registered timezones + for (let tzid of Object.keys(reqTzid)) { + if (!vtimezones[tzid] && TimezoneService.has(tzid)) { + vcal.addSubcomponent(TimezoneService.get(tzid).component); + } + } + + return vcal; +} + +/** + * Checks if the given type is of the number type and also NaN. + * + * @param {Number} number The number to check + * @return {Boolean} True, if the number is strictly NaN + */ +export function isStrictlyNaN(number) { + return typeof(number) === 'number' && isNaN(number); +} + +/** + * Parses a string value that is expected to be an integer, when the valid is + * not an integer throws a decoration error. + * + * @param {String} string Raw string input + * @return {Number} Parsed integer + */ +export function strictParseInt(string) { + let result = parseInt(string, 10); + + if (isStrictlyNaN(result)) { + throw new Error( + 'Could not extract integer from "' + string + '"' + ); + } + + return result; +} + +/** + * Creates or returns a class instance of a given type with the initialization + * data if the data is not already an instance of the given type. + * + * @example + * var time = new ICAL.Time(...); + * var result = ICAL.helpers.formatClassType(time, ICAL.Time); + * + * (result instanceof ICAL.Time) + * // => true + * + * result = ICAL.helpers.formatClassType({}, ICAL.Time); + * (result isntanceof ICAL.Time) + * // => true + * + * + * @param {Object} data object initialization data + * @param {Object} type object type (like ICAL.Time) + * @return {?} An instance of the found type. + */ +export function formatClassType(data, type) { + if (typeof(data) === 'undefined') { + return undefined; + } + + if (data instanceof type) { + return data; + } + return new type(data); +} + +/** + * Identical to indexOf but will only match values when they are not preceded + * by a backslash character. + * + * @param {String} buffer String to search + * @param {String} search Value to look for + * @param {Number} pos Start position + * @return {Number} The position, or -1 if not found + */ +export function unescapedIndexOf(buffer, search, pos) { + while ((pos = buffer.indexOf(search, pos)) !== -1) { + if (pos > 0 && buffer[pos - 1] === '\\') { + pos += 1; + } else { + return pos; + } + } + return -1; +} + +/** + * Find the index for insertion using binary search. + * + * @param {Array} list The list to search + * @param {?} seekVal The value to insert + * @param {function(?,?)} cmpfunc The comparison func, that can + * compare two seekVals + * @return {Number} The insert position + */ +export function binsearchInsert(list, seekVal, cmpfunc) { + if (!list.length) + return 0; + + let low = 0, high = list.length - 1, + mid, cmpval; + + while (low <= high) { + mid = low + Math.floor((high - low) / 2); + cmpval = cmpfunc(seekVal, list[mid]); + + if (cmpval < 0) + high = mid - 1; + else if (cmpval > 0) + low = mid + 1; + else + break; + } + + if (cmpval < 0) + return mid; // insertion is displacing, so use mid outright. + else if (cmpval > 0) + return mid + 1; + else + return mid; +} + +/** + * Clone the passed object or primitive. By default a shallow clone will be + * executed. + * + * @param {*} aSrc The thing to clone + * @param {Boolean=} aDeep If true, a deep clone will be performed + * @return {*} The copy of the thing + */ +export function clone(aSrc, aDeep) { + if (!aSrc || typeof aSrc != "object") { + return aSrc; + } else if (aSrc instanceof Date) { + return new Date(aSrc.getTime()); + } else if ("clone" in aSrc) { + return aSrc.clone(); + } else if (Array.isArray(aSrc)) { + let arr = []; + for (let i = 0; i < aSrc.length; i++) { + arr.push(aDeep ? clone(aSrc[i], true) : aSrc[i]); + } + return arr; + } else { + let obj = {}; + for (let [name, value] of Object.entries(aSrc)) { + if (aDeep) { + obj[name] = clone(value, true); + } else { + obj[name] = value; + } + } + return obj; + } +} + +/** + * Performs iCalendar line folding. A line ending character is inserted and + * the next line begins with a whitespace. + * + * @example + * SUMMARY:This line will be fold + * ed right in the middle of a word. + * + * @param {String} aLine The line to fold + * @return {String} The folded line + */ +export function foldline(aLine) { + let result = ""; + let line = aLine || "", pos = 0, line_length = 0; + //pos counts position in line for the UTF-16 presentation + //line_length counts the bytes for the UTF-8 presentation + while (line.length) { + let cp = line.codePointAt(pos); + if (cp < 128) ++line_length; + else if (cp < 2048) line_length += 2;//needs 2 UTF-8 bytes + else if (cp < 65536) line_length += 3; + else line_length += 4; //cp is less than 1114112 + if (line_length < ICALmodule.foldLength + 1) + pos += cp > 65535 ? 2 : 1; + else { + result += ICALmodule.newLineChar + " " + line.slice(0, Math.max(0, pos)); + line = line.slice(Math.max(0, pos)); + pos = line_length = 0; + } + } + return result.slice(ICALmodule.newLineChar.length + 1); +} + +/** + * Pads the given string or number with zeros so it will have at least two + * characters. + * + * @param {String|Number} data The string or number to pad + * @return {String} The number padded as a string + */ +export function pad2(data) { + if (typeof(data) !== 'string') { + // handle fractions. + if (typeof(data) === 'number') { + data = parseInt(data); + } + data = String(data); + } + + let len = data.length; + + switch (len) { + case 0: + return '00'; + case 1: + return '0' + data; + default: + return data; + } +} + +/** + * Truncates the given number, correctly handling negative numbers. + * + * @param {Number} number The number to truncate + * @return {Number} The truncated number + */ +export function trunc(number) { + return (number < 0 ? Math.ceil(number) : Math.floor(number)); +} + +/** + * Poor-man's cross-browser object extension. Doesn't support all the + * features, but enough for our usage. Note that the target's properties are + * not overwritten with the source properties. + * + * @example + * var child = ICAL.helpers.extend(parent, { + * "bar": 123 + * }); + * + * @param {Object} source The object to extend + * @param {Object} target The object to extend with + * @return {Object} Returns the target. + */ +export function extend(source, target) { + for (let key in source) { + let descr = Object.getOwnPropertyDescriptor(source, key); + if (descr && !Object.getOwnPropertyDescriptor(target, key)) { + Object.defineProperty(target, key, descr); + } + } + return target; +} diff --git a/lib/ical/module.js b/lib/ical/module.js new file mode 100644 index 0000000..85858d7 --- /dev/null +++ b/lib/ical/module.js @@ -0,0 +1,73 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */ + +import Binary from "./binary.js"; +import Component from "./component.js"; +import ComponentParser from "./component_parser.js"; +import design from "./design.js"; +import Duration from "./duration.js"; +import Event from "./event.js"; +import * as helpers from "./helpers.js"; +import parse from "./parse.js"; +import Period from "./period.js"; +import Property from "./property.js"; +import Recur from "./recur.js"; +import RecurExpansion from "./recur_expansion.js"; +import RecurIterator from "./recur_iterator.js"; +import stringify from "./stringify.js"; +import Time from "./time.js"; +import Timezone from "./timezone.js"; +import TimezoneService from "./timezone_service.js"; +import UtcOffset from "./utc_offset.js"; +import VCardTime from "./vcard_time.js"; + +/** + * The main ICAL module. Provides access to everything else. + * + * @alias ICAL + * @namespace ICAL + * @property {ICAL.design} design + * @property {ICAL.helpers} helpers + */ +export default { + /** + * The number of characters before iCalendar line folding should occur + * @type {Number} + * @default 75 + */ + foldLength: 75, + + debug: false, + + /** + * The character(s) to be used for a newline. The default value is provided by + * rfc5545. + * @type {String} + * @default "\r\n" + */ + newLineChar: '\r\n', + + Binary, + Component, + ComponentParser, + Duration, + Event, + Period, + Property, + Recur, + RecurExpansion, + RecurIterator, + Time, + Timezone, + TimezoneService, + UtcOffset, + VCardTime, + + parse, + stringify, + + design, + helpers +}; diff --git a/lib/ical/parse.js b/lib/ical/parse.js new file mode 100644 index 0000000..29cde73 --- /dev/null +++ b/lib/ical/parse.js @@ -0,0 +1,547 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */ + +import design from "./design.js"; +import { unescapedIndexOf } from "./helpers.js"; + +/** + * This lets typescript resolve our custom types in the + * generated d.ts files (jsdoc typedefs are converted to typescript types). + * Ignore prevents the typedefs from being documented more than once. + * + * @ignore + * @typedef {import("./types.js").parserState} parserState + * Imports the 'parserState' type from the "types.js" module + * @typedef {import("./types.js").designSet} designSet + * Imports the 'designSet' type from the "types.js" module + */ + +const CHAR = /[^ \t]/; +const VALUE_DELIMITER = ':'; +const PARAM_DELIMITER = ';'; +const PARAM_NAME_DELIMITER = '='; +const DEFAULT_VALUE_TYPE = 'unknown'; +const DEFAULT_PARAM_TYPE = 'text'; +const RFC6868_REPLACE_MAP = { "^'": '"', "^n": "\n", "^^": "^" }; + +/** + * Parses iCalendar or vCard data into a raw jCal object. Consult + * documentation on the {@tutorial layers|layers of parsing} for more + * details. + * + * @function ICAL.parse + * @memberof ICAL + * @variation function + * @todo Fix the API to be more clear on the return type + * @param {String} input The string data to parse + * @return {Object|Object[]} A single jCal object, or an array thereof + */ +export default function parse(input) { + let state = {}; + let root = state.component = []; + + state.stack = [root]; + + parse._eachLine(input, function(err, line) { + parse._handleContentLine(line, state); + }); + + + // when there are still items on the stack + // throw a fatal error, a component was not closed + // correctly in that case. + if (state.stack.length > 1) { + throw new ParserError( + 'invalid ical body. component began but did not end' + ); + } + + state = null; + + return (root.length == 1 ? root[0] : root); +} + +/** + * Parse an iCalendar property value into the jCal for a single property + * + * @function ICAL.parse.property + * @param {String} str + * The iCalendar property string to parse + * @param {designSet=} designSet + * The design data to use for this property + * @return {Object} + * The jCal Object containing the property + */ +parse.property = function(str, designSet) { + let state = { + component: [[], []], + designSet: designSet || design.defaultSet + }; + parse._handleContentLine(str, state); + return state.component[1][0]; +}; + +/** + * Convenience method to parse a component. You can use ICAL.parse() directly + * instead. + * + * @function ICAL.parse.component + * @see ICAL.parse(function) + * @param {String} str The iCalendar component string to parse + * @return {Object} The jCal Object containing the component + */ +parse.component = function(str) { + return parse(str); +}; + + +/** + * An error that occurred during parsing. + * + * @param {String} message The error message + * @memberof ICAL.parse + * @extends {Error} + */ +class ParserError extends Error { + name = this.constructor.name; +} + +// classes & constants +parse.ParserError = ParserError; + + +/** + * Handles a single line of iCalendar/vCard, updating the state. + * + * @private + * @function ICAL.parse._handleContentLine + * @param {String} line The content line to process + * @param {parserState} state The current state of the line parsing + */ +parse._handleContentLine = function(line, state) { + // break up the parts of the line + let valuePos = line.indexOf(VALUE_DELIMITER); + let paramPos = line.indexOf(PARAM_DELIMITER); + + let lastParamIndex; + let lastValuePos; + + // name of property or begin/end + let name; + let value; + // params is only overridden if paramPos !== -1. + // we can't do params = params || {} later on + // because it sacrifices ops. + let params = {}; + + /** + * Different property cases + * + * + * 1. RRULE:FREQ=foo + * // FREQ= is not a param but the value + * + * 2. ATTENDEE;ROLE=REQ-PARTICIPANT; + * // ROLE= is a param because : has not happened yet + */ + // when the parameter delimiter is after the + // value delimiter then it is not a parameter. + + if ((paramPos !== -1 && valuePos !== -1)) { + // when the parameter delimiter is after the + // value delimiter then it is not a parameter. + if (paramPos > valuePos) { + paramPos = -1; + } + } + + let parsedParams; + if (paramPos !== -1) { + name = line.slice(0, Math.max(0, paramPos)).toLowerCase(); + parsedParams = parse._parseParameters(line.slice(Math.max(0, paramPos)), 0, state.designSet); + if (parsedParams[2] == -1) { + throw new ParserError("Invalid parameters in '" + line + "'"); + } + params = parsedParams[0]; + lastParamIndex = parsedParams[1].length + parsedParams[2] + paramPos; + if ((lastValuePos = + line.slice(Math.max(0, lastParamIndex)).indexOf(VALUE_DELIMITER)) !== -1) { + value = line.slice(Math.max(0, lastParamIndex + lastValuePos + 1)); + } else { + throw new ParserError("Missing parameter value in '" + line + "'"); + } + } else if (valuePos !== -1) { + // without parmeters (BEGIN:VCAENDAR, CLASS:PUBLIC) + name = line.slice(0, Math.max(0, valuePos)).toLowerCase(); + value = line.slice(Math.max(0, valuePos + 1)); + + if (name === 'begin') { + let newComponent = [value.toLowerCase(), [], []]; + if (state.stack.length === 1) { + state.component.push(newComponent); + } else { + state.component[2].push(newComponent); + } + state.stack.push(state.component); + state.component = newComponent; + if (!state.designSet) { + state.designSet = design.getDesignSet(state.component[0]); + } + return; + } else if (name === 'end') { + state.component = state.stack.pop(); + return; + } + // If it is not begin/end, then this is a property with an empty value, + // which should be considered valid. + } else { + /** + * Invalid line. + * The rational to throw an error is we will + * never be certain that the rest of the file + * is sane and it is unlikely that we can serialize + * the result correctly either. + */ + throw new ParserError( + 'invalid line (no token ";" or ":") "' + line + '"' + ); + } + + let valueType; + let multiValue = false; + let structuredValue = false; + let propertyDetails; + let splitName; + let ungroupedName; + + // fetch the ungrouped part of the name + if (state.designSet.propertyGroups && name.indexOf('.') !== -1) { + splitName = name.split('.'); + params.group = splitName[0]; + ungroupedName = splitName[1]; + } else { + ungroupedName = name; + } + + if (ungroupedName in state.designSet.property) { + propertyDetails = state.designSet.property[ungroupedName]; + + if ('multiValue' in propertyDetails) { + multiValue = propertyDetails.multiValue; + } + + if ('structuredValue' in propertyDetails) { + structuredValue = propertyDetails.structuredValue; + } + + if (value && 'detectType' in propertyDetails) { + valueType = propertyDetails.detectType(value); + } + } + + // attempt to determine value + if (!valueType) { + if (!('value' in params)) { + if (propertyDetails) { + valueType = propertyDetails.defaultType; + } else { + valueType = DEFAULT_VALUE_TYPE; + } + } else { + // possible to avoid this? + valueType = params.value.toLowerCase(); + } + } + + delete params.value; + + /** + * Note on `var result` juggling: + * + * I observed that building the array in pieces has adverse + * effects on performance, so where possible we inline the creation. + * It is a little ugly but resulted in ~2000 additional ops/sec. + */ + + let result; + if (multiValue && structuredValue) { + value = parse._parseMultiValue(value, structuredValue, valueType, [], multiValue, state.designSet, structuredValue); + result = [ungroupedName, params, valueType, value]; + } else if (multiValue) { + result = [ungroupedName, params, valueType]; + parse._parseMultiValue(value, multiValue, valueType, result, null, state.designSet, false); + } else if (structuredValue) { + value = parse._parseMultiValue(value, structuredValue, valueType, [], null, state.designSet, structuredValue); + result = [ungroupedName, params, valueType, value]; + } else { + value = parse._parseValue(value, valueType, state.designSet, false); + result = [ungroupedName, params, valueType, value]; + } + // rfc6350 requires that in vCard 4.0 the first component is the VERSION + // component with as value 4.0, note that 3.0 does not have this requirement. + if (state.component[0] === 'vcard' && state.component[1].length === 0 && + !(name === 'version' && value === '4.0')) { + state.designSet = design.getDesignSet("vcard3"); + } + state.component[1].push(result); +}; + +/** + * Parse a value from the raw value into the jCard/jCal value. + * + * @private + * @function ICAL.parse._parseValue + * @param {String} value Original value + * @param {String} type Type of value + * @param {Object} designSet The design data to use for this value + * @return {Object} varies on type + */ +parse._parseValue = function(value, type, designSet, structuredValue) { + if (type in designSet.value && 'fromICAL' in designSet.value[type]) { + return designSet.value[type].fromICAL(value, structuredValue); + } + return value; +}; + +/** + * Parse parameters from a string to object. + * + * @function ICAL.parse._parseParameters + * @private + * @param {String} line A single unfolded line + * @param {Number} start Position to start looking for properties + * @param {Object} designSet The design data to use for this property + * @return {Object} key/value pairs + */ +parse._parseParameters = function(line, start, designSet) { + let lastParam = start; + let pos = 0; + let delim = PARAM_NAME_DELIMITER; + let result = {}; + let name, lcname; + let value, valuePos = -1; + let type, multiValue, mvdelim; + + // find the next '=' sign + // use lastParam and pos to find name + // check if " is used if so get value from "->" + // then increment pos to find next ; + + while ((pos !== false) && + (pos = line.indexOf(delim, pos + 1)) !== -1) { + + name = line.slice(lastParam + 1, pos); + if (name.length == 0) { + throw new ParserError("Empty parameter name in '" + line + "'"); + } + lcname = name.toLowerCase(); + mvdelim = false; + multiValue = false; + + if (lcname in designSet.param && designSet.param[lcname].valueType) { + type = designSet.param[lcname].valueType; + } else { + type = DEFAULT_PARAM_TYPE; + } + + if (lcname in designSet.param) { + multiValue = designSet.param[lcname].multiValue; + if (designSet.param[lcname].multiValueSeparateDQuote) { + mvdelim = parse._rfc6868Escape('"' + multiValue + '"'); + } + } + + let nextChar = line[pos + 1]; + if (nextChar === '"') { + valuePos = pos + 2; + pos = line.indexOf('"', valuePos); + if (multiValue && pos != -1) { + let extendedValue = true; + while (extendedValue) { + if (line[pos + 1] == multiValue && line[pos + 2] == '"') { + pos = line.indexOf('"', pos + 3); + } else { + extendedValue = false; + } + } + } + if (pos === -1) { + throw new ParserError( + 'invalid line (no matching double quote) "' + line + '"' + ); + } + value = line.slice(valuePos, pos); + lastParam = line.indexOf(PARAM_DELIMITER, pos); + let propValuePos = line.indexOf(VALUE_DELIMITER, pos); + // if either no next parameter or delimeter in property value, let's stop here + if (lastParam === -1 || (propValuePos !== -1 && lastParam > propValuePos)) { + pos = false; + } + } else { + valuePos = pos + 1; + + // move to next ";" + let nextPos = line.indexOf(PARAM_DELIMITER, valuePos); + let propValuePos = line.indexOf(VALUE_DELIMITER, valuePos); + if (propValuePos !== -1 && nextPos > propValuePos) { + // this is a delimiter in the property value, let's stop here + nextPos = propValuePos; + pos = false; + } else if (nextPos === -1) { + // no ";" + if (propValuePos === -1) { + nextPos = line.length; + } else { + nextPos = propValuePos; + } + pos = false; + } else { + lastParam = nextPos; + pos = nextPos; + } + + value = line.slice(valuePos, nextPos); + } + + const length_before = value.length; + value = parse._rfc6868Escape(value); + valuePos += length_before - value.length; + if (multiValue) { + let delimiter = mvdelim || multiValue; + value = parse._parseMultiValue(value, delimiter, type, [], null, designSet); + } else { + value = parse._parseValue(value, type, designSet); + } + + if (multiValue && (lcname in result)) { + if (Array.isArray(result[lcname])) { + result[lcname].push(value); + } else { + result[lcname] = [ + result[lcname], + value + ]; + } + } else { + result[lcname] = value; + } + } + return [result, value, valuePos]; +}; + +/** + * Internal helper for rfc6868. Exposing this on ICAL.parse so that + * hackers can disable the rfc6868 parsing if the really need to. + * + * @function ICAL.parse._rfc6868Escape + * @param {String} val The value to escape + * @return {String} The escaped value + */ +parse._rfc6868Escape = function(val) { + return val.replace(/\^['n^]/g, function(x) { + return RFC6868_REPLACE_MAP[x]; + }); +}; + +/** + * Parse a multi value string. This function is used either for parsing + * actual multi-value property's values, or for handling parameter values. It + * can be used for both multi-value properties and structured value properties. + * + * @private + * @function ICAL.parse._parseMultiValue + * @param {String} buffer The buffer containing the full value + * @param {String} delim The multi-value delimiter + * @param {String} type The value type to be parsed + * @param {Array.} result The array to append results to, varies on value type + * @param {String} innerMulti The inner delimiter to split each value with + * @param {designSet} designSet The design data for this value + * @return {?|Array.} Either an array of results, or the first result + */ +parse._parseMultiValue = function(buffer, delim, type, result, innerMulti, designSet, structuredValue) { + let pos = 0; + let lastPos = 0; + let value; + if (delim.length === 0) { + return buffer; + } + + // split each piece + while ((pos = unescapedIndexOf(buffer, delim, lastPos)) !== -1) { + value = buffer.slice(lastPos, pos); + if (innerMulti) { + value = parse._parseMultiValue(value, innerMulti, type, [], null, designSet, structuredValue); + } else { + value = parse._parseValue(value, type, designSet, structuredValue); + } + result.push(value); + lastPos = pos + delim.length; + } + + // on the last piece take the rest of string + value = buffer.slice(lastPos); + if (innerMulti) { + value = parse._parseMultiValue(value, innerMulti, type, [], null, designSet, structuredValue); + } else { + value = parse._parseValue(value, type, designSet, structuredValue); + } + result.push(value); + + return result.length == 1 ? result[0] : result; +}; + +/** + * Process a complete buffer of iCalendar/vCard data line by line, correctly + * unfolding content. Each line will be processed with the given callback + * + * @private + * @function ICAL.parse._eachLine + * @param {String} buffer The buffer to process + * @param {function(?String, String)} callback The callback for each line + */ +parse._eachLine = function(buffer, callback) { + let len = buffer.length; + let lastPos = buffer.search(CHAR); + let pos = lastPos; + let line; + let firstChar; + + let newlineOffset; + + do { + pos = buffer.indexOf('\n', lastPos) + 1; + + if (pos > 1 && buffer[pos - 2] === '\r') { + newlineOffset = 2; + } else { + newlineOffset = 1; + } + + if (pos === 0) { + pos = len; + newlineOffset = 0; + } + + firstChar = buffer[lastPos]; + + if (firstChar === ' ' || firstChar === '\t') { + // add to line + line += buffer.slice(lastPos + 1, pos - newlineOffset); + } else { + if (line) + callback(null, line); + // push line + line = buffer.slice(lastPos, pos - newlineOffset); + } + + lastPos = pos; + } while (pos !== len); + + // extra ending line + line = line.trim(); + + if (line.length) + callback(null, line); +}; diff --git a/lib/ical/period.js b/lib/ical/period.js new file mode 100644 index 0000000..03ff6e2 --- /dev/null +++ b/lib/ical/period.js @@ -0,0 +1,245 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */ + +import Time from "./time.js"; +import Duration from "./duration.js"; +// needed for typescript type resolution +// eslint-disable-next-line no-unused-vars +import Property from "./property.js"; + +/** + * This lets typescript resolve our custom types in the + * generated d.ts files (jsdoc typedefs are converted to typescript types). + * Ignore prevents the typedefs from being documented more than once. + * @ignore + * @typedef {import("./types.js").jCalComponent} jCalComponent + * Imports the 'occurrenceDetails' type from the "types.js" module + */ + +/** + * This class represents the "period" value type, with various calculation and manipulation methods. + * + * @memberof ICAL + */ +class Period { + /** + * Creates a new {@link ICAL.Period} instance from the passed string. + * + * @param {String} str The string to parse + * @param {Property} prop The property this period will be on + * @return {Period} The created period instance + */ + static fromString(str, prop) { + let parts = str.split('/'); + + if (parts.length !== 2) { + throw new Error( + 'Invalid string value: "' + str + '" must contain a "/" char.' + ); + } + + let options = { + start: Time.fromDateTimeString(parts[0], prop) + }; + + let end = parts[1]; + + if (Duration.isValueString(end)) { + options.duration = Duration.fromString(end); + } else { + options.end = Time.fromDateTimeString(end, prop); + } + + return new Period(options); + } + + /** + * Creates a new {@link ICAL.Period} instance from the given data object. + * The passed data object cannot contain both and end date and a duration. + * + * @param {Object} aData An object with members of the period + * @param {Time=} aData.start The start of the period + * @param {Time=} aData.end The end of the period + * @param {Duration=} aData.duration The duration of the period + * @return {Period} The period instance + */ + static fromData(aData) { + return new Period(aData); + } + + /** + * Returns a new period instance from the given jCal data array. The first + * member is always the start date string, the second member is either a + * duration or end date string. + * + * @param {jCalComponent} aData The jCal data array + * @param {Property} aProp The property this jCal data is on + * @param {Boolean} aLenient If true, data value can be both date and date-time + * @return {Period} The period instance + */ + static fromJSON(aData, aProp, aLenient) { + function fromDateOrDateTimeString(aValue, dateProp) { + if (aLenient) { + return Time.fromString(aValue, dateProp); + } else { + return Time.fromDateTimeString(aValue, dateProp); + } + } + + if (Duration.isValueString(aData[1])) { + return Period.fromData({ + start: fromDateOrDateTimeString(aData[0], aProp), + duration: Duration.fromString(aData[1]) + }); + } else { + return Period.fromData({ + start: fromDateOrDateTimeString(aData[0], aProp), + end: fromDateOrDateTimeString(aData[1], aProp) + }); + } + } + + /** + * Creates a new ICAL.Period instance. The passed data object cannot contain both and end date and + * a duration. + * + * @param {Object} aData An object with members of the period + * @param {Time=} aData.start The start of the period + * @param {Time=} aData.end The end of the period + * @param {Duration=} aData.duration The duration of the period + */ + constructor(aData) { + this.wrappedJSObject = this; + + if (aData && 'start' in aData) { + if (aData.start && !(aData.start instanceof Time)) { + throw new TypeError('.start must be an instance of ICAL.Time'); + } + this.start = aData.start; + } + + if (aData && aData.end && aData.duration) { + throw new Error('cannot accept both end and duration'); + } + + if (aData && 'end' in aData) { + if (aData.end && !(aData.end instanceof Time)) { + throw new TypeError('.end must be an instance of ICAL.Time'); + } + this.end = aData.end; + } + + if (aData && 'duration' in aData) { + if (aData.duration && !(aData.duration instanceof Duration)) { + throw new TypeError('.duration must be an instance of ICAL.Duration'); + } + this.duration = aData.duration; + } + } + + + /** + * The start of the period + * @type {Time} + */ + start = null; + + /** + * The end of the period + * @type {Time} + */ + end = null; + + /** + * The duration of the period + * @type {Duration} + */ + duration = null; + + /** + * The class identifier. + * @constant + * @type {String} + * @default "icalperiod" + */ + icalclass = "icalperiod"; + + /** + * The type name, to be used in the jCal object. + * @constant + * @type {String} + * @default "period" + */ + icaltype = "period"; + + /** + * Returns a clone of the duration object. + * + * @return {Period} The cloned object + */ + clone() { + return Period.fromData({ + start: this.start ? this.start.clone() : null, + end: this.end ? this.end.clone() : null, + duration: this.duration ? this.duration.clone() : null + }); + } + + /** + * Calculates the duration of the period, either directly or by subtracting + * start from end date. + * + * @return {Duration} The calculated duration + */ + getDuration() { + if (this.duration) { + return this.duration; + } else { + return this.end.subtractDate(this.start); + } + } + + /** + * Calculates the end date of the period, either directly or by adding + * duration to start date. + * + * @return {Time} The calculated end date + */ + getEnd() { + if (this.end) { + return this.end; + } else { + let end = this.start.clone(); + end.addDuration(this.duration); + return end; + } + } + + /** + * The string representation of this period. + * @return {String} + */ + toString() { + return this.start + "/" + (this.end || this.duration); + } + + /** + * The jCal representation of this period type. + * @return {Object} + */ + toJSON() { + return [this.start.toString(), (this.end || this.duration).toString()]; + } + + /** + * The iCalendar string representation of this period. + * @return {String} + */ + toICALString() { + return this.start.toICALString() + "/" + + (this.end || this.duration).toICALString(); + } +} +export default Period; diff --git a/lib/ical/property.js b/lib/ical/property.js new file mode 100644 index 0000000..3ded381 --- /dev/null +++ b/lib/ical/property.js @@ -0,0 +1,439 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */ + +const NAME_INDEX = 0; +const PROP_INDEX = 1; +const TYPE_INDEX = 2; +const VALUE_INDEX = 3; + +import design from "./design.js"; +import ICALStringify from "./stringify.js"; +import ICALParse from "./parse.js"; + +// needed for typescript type resolution +// eslint-disable-next-line no-unused-vars +import Component from "./component.js"; +// needed for typescript type resolution +// eslint-disable-next-line no-unused-vars +import Duration from "./duration.js"; +// needed for typescript type resolution +// eslint-disable-next-line no-unused-vars +import UtcOffset from "./utc_offset.js"; +// needed for typescript type resolution +// eslint-disable-next-line no-unused-vars +import Binary from "./binary.js"; +// needed for typescript type resolution +// eslint-disable-next-line no-unused-vars +import Period from "./period.js"; +// needed for typescript type resolution +// eslint-disable-next-line no-unused-vars +import Recur from "./recur.js"; +// needed for typescript type resolution +// eslint-disable-next-line no-unused-vars +import Time from "./time.js"; + +/** + * This lets typescript resolve our custom types in the + * generated d.ts files (jsdoc typedefs are converted to typescript types). + * Ignore prevents the typedefs from being documented more than once. + * @ignore + * @typedef {import("./types.js").designSet} designSet + * Imports the 'designSet' type from the "types.js" module + * @typedef {import("./types.js").Geo} Geo + * Imports the 'Geo' type from the "types.js" module + */ + +/** + * Provides a layer on top of the raw jCal object for manipulating a single property, with its + * parameters and value. + * + * @memberof ICAL + */ +class Property { + /** + * Create an {@link ICAL.Property} by parsing the passed iCalendar string. + * + * @param {String} str The iCalendar string to parse + * @param {designSet=} designSet The design data to use for this property + * @return {Property} The created iCalendar property + */ + static fromString(str, designSet) { + return new Property(ICALParse.property(str, designSet)); + } + + /** + * Creates a new ICAL.Property instance. + * + * It is important to note that mutations done in the wrapper directly mutate the jCal object used + * to initialize. + * + * Can also be used to create new properties by passing the name of the property (as a String). + * + * @param {Array|String} jCal Raw jCal representation OR the new name of the property + * @param {Component=} parent Parent component + */ + constructor(jCal, parent) { + this._parent = parent || null; + + if (typeof(jCal) === 'string') { + // We are creating the property by name and need to detect the type + this.jCal = [jCal, {}, design.defaultType]; + this.jCal[TYPE_INDEX] = this.getDefaultType(); + } else { + this.jCal = jCal; + } + this._updateType(); + } + + /** + * The value type for this property + * @type {String} + */ + get type() { + return this.jCal[TYPE_INDEX]; + } + + /** + * The name of this property, in lowercase. + * @type {String} + */ + get name() { + return this.jCal[NAME_INDEX]; + } + + /** + * The parent component for this property. + * @type {Component} + */ + get parent() { + return this._parent; + } + + set parent(p) { + // Before setting the parent, check if the design set has changed. If it + // has, we later need to update the type if it was unknown before. + let designSetChanged = !this._parent || (p && p._designSet != this._parent._designSet); + + this._parent = p; + + if (this.type == design.defaultType && designSetChanged) { + this.jCal[TYPE_INDEX] = this.getDefaultType(); + this._updateType(); + } + } + + /** + * The design set for this property, e.g. icalendar vs vcard + * + * @type {designSet} + * @private + */ + get _designSet() { + return this.parent ? this.parent._designSet : design.defaultSet; + } + + /** + * Updates the type metadata from the current jCal type and design set. + * + * @private + */ + _updateType() { + let designSet = this._designSet; + + if (this.type in designSet.value) { + if ('decorate' in designSet.value[this.type]) { + this.isDecorated = true; + } else { + this.isDecorated = false; + } + + if (this.name in designSet.property) { + this.isMultiValue = ('multiValue' in designSet.property[this.name]); + this.isStructuredValue = ('structuredValue' in designSet.property[this.name]); + } + } + } + + /** + * Hydrate a single value. The act of hydrating means turning the raw jCal + * value into a potentially wrapped object, for example {@link ICAL.Time}. + * + * @private + * @param {Number} index The index of the value to hydrate + * @return {?Object} The decorated value. + */ + _hydrateValue(index) { + if (this._values && this._values[index]) { + return this._values[index]; + } + + // for the case where there is no value. + if (this.jCal.length <= (VALUE_INDEX + index)) { + return null; + } + + if (this.isDecorated) { + if (!this._values) { + this._values = []; + } + return (this._values[index] = this._decorate( + this.jCal[VALUE_INDEX + index] + )); + } else { + return this.jCal[VALUE_INDEX + index]; + } + } + + /** + * Decorate a single value, returning its wrapped object. This is used by + * the hydrate function to actually wrap the value. + * + * @private + * @param {?} value The value to decorate + * @return {Object} The decorated value + */ + _decorate(value) { + return this._designSet.value[this.type].decorate(value, this); + } + + /** + * Undecorate a single value, returning its raw jCal data. + * + * @private + * @param {Object} value The value to undecorate + * @return {?} The undecorated value + */ + _undecorate(value) { + return this._designSet.value[this.type].undecorate(value, this); + } + + /** + * Sets the value at the given index while also hydrating it. The passed + * value can either be a decorated or undecorated value. + * + * @private + * @param {?} value The value to set + * @param {Number} index The index to set it at + */ + _setDecoratedValue(value, index) { + if (!this._values) { + this._values = []; + } + + if (typeof(value) === 'object' && 'icaltype' in value) { + // decorated value + this.jCal[VALUE_INDEX + index] = this._undecorate(value); + this._values[index] = value; + } else { + // undecorated value + this.jCal[VALUE_INDEX + index] = value; + this._values[index] = this._decorate(value); + } + } + + /** + * Gets a parameter on the property. + * + * @param {String} name Parameter name (lowercase) + * @return {Array|String} Parameter value + */ + getParameter(name) { + if (name in this.jCal[PROP_INDEX]) { + return this.jCal[PROP_INDEX][name]; + } else { + return undefined; + } + } + + /** + * Gets first parameter on the property. + * + * @param {String} name Parameter name (lowercase) + * @return {String} Parameter value + */ + getFirstParameter(name) { + let parameters = this.getParameter(name); + + if (Array.isArray(parameters)) { + return parameters[0]; + } + + return parameters; + } + + /** + * Sets a parameter on the property. + * + * @param {String} name The parameter name + * @param {Array|String} value The parameter value + */ + setParameter(name, value) { + let lcname = name.toLowerCase(); + if (typeof value === "string" && + lcname in this._designSet.param && + 'multiValue' in this._designSet.param[lcname]) { + value = [value]; + } + this.jCal[PROP_INDEX][name] = value; + } + + /** + * Removes a parameter + * + * @param {String} name The parameter name + */ + removeParameter(name) { + delete this.jCal[PROP_INDEX][name]; + } + + /** + * Get the default type based on this property's name. + * + * @return {String} The default type for this property + */ + getDefaultType() { + let name = this.jCal[NAME_INDEX]; + let designSet = this._designSet; + + if (name in designSet.property) { + let details = designSet.property[name]; + if ('defaultType' in details) { + return details.defaultType; + } + } + return design.defaultType; + } + + /** + * Sets type of property and clears out any existing values of the current + * type. + * + * @param {String} type New iCAL type (see design.*.values) + */ + resetType(type) { + this.removeAllValues(); + this.jCal[TYPE_INDEX] = type; + this._updateType(); + } + + /** + * Finds the first property value. + * + * @return {Binary | Duration | Period | + * Recur | Time | UtcOffset | Geo | string | null} First property value + */ + getFirstValue() { + return this._hydrateValue(0); + } + + /** + * Gets all values on the property. + * + * NOTE: this creates an array during each call. + * + * @return {Array} List of values + */ + getValues() { + let len = this.jCal.length - VALUE_INDEX; + + if (len < 1) { + // it is possible for a property to have no value. + return []; + } + + let i = 0; + let result = []; + + for (; i < len; i++) { + result[i] = this._hydrateValue(i); + } + + return result; + } + + /** + * Removes all values from this property + */ + removeAllValues() { + if (this._values) { + this._values.length = 0; + } + this.jCal.length = 3; + } + + /** + * Sets the values of the property. Will overwrite the existing values. + * This can only be used for multi-value properties. + * + * @param {Array} values An array of values + */ + setValues(values) { + if (!this.isMultiValue) { + throw new Error( + this.name + ': does not not support mulitValue.\n' + + 'override isMultiValue' + ); + } + + let len = values.length; + let i = 0; + this.removeAllValues(); + + if (len > 0 && + typeof(values[0]) === 'object' && + 'icaltype' in values[0]) { + this.resetType(values[0].icaltype); + } + + if (this.isDecorated) { + for (; i < len; i++) { + this._setDecoratedValue(values[i], i); + } + } else { + for (; i < len; i++) { + this.jCal[VALUE_INDEX + i] = values[i]; + } + } + } + + /** + * Sets the current value of the property. If this is a multi-value + * property, all other values will be removed. + * + * @param {String|Object} value New property value. + */ + setValue(value) { + this.removeAllValues(); + if (typeof(value) === 'object' && 'icaltype' in value) { + this.resetType(value.icaltype); + } + + if (this.isDecorated) { + this._setDecoratedValue(value, 0); + } else { + this.jCal[VALUE_INDEX] = value; + } + } + + /** + * Returns the Object representation of this component. The returned object + * is a live jCal object and should be cloned if modified. + * @return {Object} + */ + toJSON() { + return this.jCal; + } + + /** + * The string representation of this component. + * @return {String} + */ + toICALString() { + return ICALStringify.property( + this.jCal, this._designSet, true + ); + } +} +export default Property; diff --git a/lib/ical/recur.js b/lib/ical/recur.js new file mode 100644 index 0000000..79c88ad --- /dev/null +++ b/lib/ical/recur.js @@ -0,0 +1,576 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */ + +import RecurIterator from "./recur_iterator.js"; +import Time from "./time.js"; +import design from "./design.js"; +import { strictParseInt, clone } from "./helpers.js"; + +/** + * This lets typescript resolve our custom types in the + * generated d.ts files (jsdoc typedefs are converted to typescript types). + * Ignore prevents the typedefs from being documented more than once. + * + * @ignore + * @typedef {import("./types.js").weekDay} weekDay + * Imports the 'weekDay' type from the "types.js" module + * @typedef {import("./types.js").frequencyValues} frequencyValues + * Imports the 'frequencyValues' type from the "types.js" module + */ + +const VALID_DAY_NAMES = /^(SU|MO|TU|WE|TH|FR|SA)$/; +const VALID_BYDAY_PART = /^([+-])?(5[0-3]|[1-4][0-9]|[1-9])?(SU|MO|TU|WE|TH|FR|SA)$/; +const DOW_MAP = { + SU: Time.SUNDAY, + MO: Time.MONDAY, + TU: Time.TUESDAY, + WE: Time.WEDNESDAY, + TH: Time.THURSDAY, + FR: Time.FRIDAY, + SA: Time.SATURDAY +}; + +const REVERSE_DOW_MAP = Object.fromEntries(Object.entries(DOW_MAP).map(entry => entry.reverse())); + +const ALLOWED_FREQ = ['SECONDLY', 'MINUTELY', 'HOURLY', + 'DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY']; + +/** + * This class represents the "recur" value type, used for example by RRULE. It provides methods to + * calculate occurrences among others. + * + * @memberof ICAL + */ +class Recur { + /** + * Creates a new {@link ICAL.Recur} instance from the passed string. + * + * @param {String} string The string to parse + * @return {Recur} The created recurrence instance + */ + static fromString(string) { + let data = this._stringToData(string, false); + return new Recur(data); + } + + /** + * Creates a new {@link ICAL.Recur} instance using members from the passed + * data object. + * + * @param {Object} aData An object with members of the recurrence + * @param {frequencyValues=} aData.freq The frequency value + * @param {Number=} aData.interval The INTERVAL value + * @param {weekDay=} aData.wkst The week start value + * @param {Time=} aData.until The end of the recurrence set + * @param {Number=} aData.count The number of occurrences + * @param {Array.=} aData.bysecond The seconds for the BYSECOND part + * @param {Array.=} aData.byminute The minutes for the BYMINUTE part + * @param {Array.=} aData.byhour The hours for the BYHOUR part + * @param {Array.=} aData.byday The BYDAY values + * @param {Array.=} aData.bymonthday The days for the BYMONTHDAY part + * @param {Array.=} aData.byyearday The days for the BYYEARDAY part + * @param {Array.=} aData.byweekno The weeks for the BYWEEKNO part + * @param {Array.=} aData.bymonth The month for the BYMONTH part + * @param {Array.=} aData.bysetpos The positionals for the BYSETPOS part + */ + static fromData(aData) { + return new Recur(aData); + } + + /** + * Converts a recurrence string to a data object, suitable for the fromData + * method. + * + * @private + * @param {String} string The string to parse + * @param {Boolean} fmtIcal If true, the string is considered to be an + * iCalendar string + * @return {Recur} The recurrence instance + */ + static _stringToData(string, fmtIcal) { + let dict = Object.create(null); + + // split is slower in FF but fast enough. + // v8 however this is faster then manual split? + let values = string.split(';'); + let len = values.length; + + for (let i = 0; i < len; i++) { + let parts = values[i].split('='); + let ucname = parts[0].toUpperCase(); + let lcname = parts[0].toLowerCase(); + let name = (fmtIcal ? lcname : ucname); + let value = parts[1]; + + if (ucname in partDesign) { + let partArr = value.split(','); + let partSet = new Set(); + + for (let part of partArr) { + partSet.add(partDesign[ucname](part)); + } + partArr = [...partSet]; + + dict[name] = (partArr.length == 1 ? partArr[0] : partArr); + } else if (ucname in optionDesign) { + optionDesign[ucname](value, dict, fmtIcal); + } else { + // Don't swallow unknown values. Just set them as they are. + dict[lcname] = value; + } + } + + return dict; + } + + /** + * Convert an ical representation of a day (SU, MO, etc..) + * into a numeric value of that day. + * + * @param {String} string The iCalendar day name + * @param {weekDay=} aWeekStart + * The week start weekday, defaults to SUNDAY + * @return {Number} Numeric value of given day + */ + static icalDayToNumericDay(string, aWeekStart) { + //XXX: this is here so we can deal + // with possibly invalid string values. + let firstDow = aWeekStart || Time.SUNDAY; + return ((DOW_MAP[string] - firstDow + 7) % 7) + 1; + } + + /** + * Convert a numeric day value into its ical representation (SU, MO, etc..) + * + * @param {Number} num Numeric value of given day + * @param {weekDay=} aWeekStart + * The week start weekday, defaults to SUNDAY + * @return {String} The ICAL day value, e.g SU,MO,... + */ + static numericDayToIcalDay(num, aWeekStart) { + //XXX: this is here so we can deal with possibly invalid number values. + // Also, this allows consistent mapping between day numbers and day + // names for external users. + let firstDow = aWeekStart || Time.SUNDAY; + let dow = (num + firstDow - Time.SUNDAY); + if (dow > 7) { + dow -= 7; + } + return REVERSE_DOW_MAP[dow]; + } + + /** + * Create a new instance of the Recur class. + * + * @param {Object} data An object with members of the recurrence + * @param {frequencyValues=} data.freq The frequency value + * @param {Number=} data.interval The INTERVAL value + * @param {weekDay=} data.wkst The week start value + * @param {Time=} data.until The end of the recurrence set + * @param {Number=} data.count The number of occurrences + * @param {Array.=} data.bysecond The seconds for the BYSECOND part + * @param {Array.=} data.byminute The minutes for the BYMINUTE part + * @param {Array.=} data.byhour The hours for the BYHOUR part + * @param {Array.=} data.byday The BYDAY values + * @param {Array.=} data.bymonthday The days for the BYMONTHDAY part + * @param {Array.=} data.byyearday The days for the BYYEARDAY part + * @param {Array.=} data.byweekno The weeks for the BYWEEKNO part + * @param {Array.=} data.bymonth The month for the BYMONTH part + * @param {Array.=} data.bysetpos The positionals for the BYSETPOS part + */ + constructor(data) { + this.wrappedJSObject = this; + this.parts = {}; + + if (data && typeof(data) === 'object') { + this.fromData(data); + } + } + + /** + * An object holding the BY-parts of the recurrence rule + * @memberof ICAL.Recur + * @typedef {Object} byParts + * @property {Array.=} BYSECOND The seconds for the BYSECOND part + * @property {Array.=} BYMINUTE The minutes for the BYMINUTE part + * @property {Array.=} BYHOUR The hours for the BYHOUR part + * @property {Array.=} BYDAY The BYDAY values + * @property {Array.=} BYMONTHDAY The days for the BYMONTHDAY part + * @property {Array.=} BYYEARDAY The days for the BYYEARDAY part + * @property {Array.=} BYWEEKNO The weeks for the BYWEEKNO part + * @property {Array.=} BYMONTH The month for the BYMONTH part + * @property {Array.=} BYSETPOS The positionals for the BYSETPOS part + */ + + /** + * An object holding the BY-parts of the recurrence rule + * @type {byParts} + */ + parts = null; + + /** + * The interval value for the recurrence rule. + * @type {Number} + */ + interval = 1; + + /** + * The week start day + * + * @type {weekDay} + * @default ICAL.Time.MONDAY + */ + wkst = Time.MONDAY; + + /** + * The end of the recurrence + * @type {?Time} + */ + until = null; + + /** + * The maximum number of occurrences + * @type {?Number} + */ + count = null; + + /** + * The frequency value. + * @type {frequencyValues} + */ + freq = null; + + /** + * The class identifier. + * @constant + * @type {String} + * @default "icalrecur" + */ + icalclass = "icalrecur"; + + /** + * The type name, to be used in the jCal object. + * @constant + * @type {String} + * @default "recur" + */ + icaltype = "recur"; + + /** + * Create a new iterator for this recurrence rule. The passed start date + * must be the start date of the event, not the start of the range to + * search in. + * + * @example + * let recur = comp.getFirstPropertyValue('rrule'); + * let dtstart = comp.getFirstPropertyValue('dtstart'); + * let iter = recur.iterator(dtstart); + * for (let next = iter.next(); next; next = iter.next()) { + * if (next.compare(rangeStart) < 0) { + * continue; + * } + * console.log(next.toString()); + * } + * + * @param {Time} aStart The item's start date + * @return {RecurIterator} The recurrence iterator + */ + iterator(aStart) { + return new RecurIterator({ + rule: this, + dtstart: aStart + }); + } + + /** + * Returns a clone of the recurrence object. + * + * @return {Recur} The cloned object + */ + clone() { + return new Recur(this.toJSON()); + } + + /** + * Checks if the current rule is finite, i.e. has a count or until part. + * + * @return {Boolean} True, if the rule is finite + */ + isFinite() { + return !!(this.count || this.until); + } + + /** + * Checks if the current rule has a count part, and not limited by an until + * part. + * + * @return {Boolean} True, if the rule is by count + */ + isByCount() { + return !!(this.count && !this.until); + } + + /** + * Adds a component (part) to the recurrence rule. This is not a component + * in the sense of {@link ICAL.Component}, but a part of the recurrence + * rule, i.e. BYMONTH. + * + * @param {String} aType The name of the component part + * @param {Array|String} aValue The component value + */ + addComponent(aType, aValue) { + let ucname = aType.toUpperCase(); + if (ucname in this.parts) { + this.parts[ucname].push(aValue); + } else { + this.parts[ucname] = [aValue]; + } + } + + /** + * Sets the component value for the given by-part. + * + * @param {String} aType The component part name + * @param {Array} aValues The component values + */ + setComponent(aType, aValues) { + this.parts[aType.toUpperCase()] = aValues.slice(); + } + + /** + * Gets (a copy) of the requested component value. + * + * @param {String} aType The component part name + * @return {Array} The component part value + */ + getComponent(aType) { + let ucname = aType.toUpperCase(); + return (ucname in this.parts ? this.parts[ucname].slice() : []); + } + + /** + * Retrieves the next occurrence after the given recurrence id. See the + * guide on {@tutorial terminology} for more details. + * + * NOTE: Currently, this method iterates all occurrences from the start + * date. It should not be called in a loop for performance reasons. If you + * would like to get more than one occurrence, you can iterate the + * occurrences manually, see the example on the + * {@link ICAL.Recur#iterator iterator} method. + * + * @param {Time} aStartTime The start of the event series + * @param {Time} aRecurrenceId The date of the last occurrence + * @return {Time} The next occurrence after + */ + getNextOccurrence(aStartTime, aRecurrenceId) { + let iter = this.iterator(aStartTime); + let next; + + do { + next = iter.next(); + } while (next && next.compare(aRecurrenceId) <= 0); + + if (next && aRecurrenceId.zone) { + next.zone = aRecurrenceId.zone; + } + + return next; + } + + /** + * Sets up the current instance using members from the passed data object. + * + * @param {Object} data An object with members of the recurrence + * @param {frequencyValues=} data.freq The frequency value + * @param {Number=} data.interval The INTERVAL value + * @param {weekDay=} data.wkst The week start value + * @param {Time=} data.until The end of the recurrence set + * @param {Number=} data.count The number of occurrences + * @param {Array.=} data.bysecond The seconds for the BYSECOND part + * @param {Array.=} data.byminute The minutes for the BYMINUTE part + * @param {Array.=} data.byhour The hours for the BYHOUR part + * @param {Array.=} data.byday The BYDAY values + * @param {Array.=} data.bymonthday The days for the BYMONTHDAY part + * @param {Array.=} data.byyearday The days for the BYYEARDAY part + * @param {Array.=} data.byweekno The weeks for the BYWEEKNO part + * @param {Array.=} data.bymonth The month for the BYMONTH part + * @param {Array.=} data.bysetpos The positionals for the BYSETPOS part + */ + fromData(data) { + for (let key in data) { + let uckey = key.toUpperCase(); + + if (uckey in partDesign) { + if (Array.isArray(data[key])) { + this.parts[uckey] = data[key]; + } else { + this.parts[uckey] = [data[key]]; + } + } else { + this[key] = data[key]; + } + } + + if (this.interval && typeof this.interval != "number") { + optionDesign.INTERVAL(this.interval, this); + } + + if (this.wkst && typeof this.wkst != "number") { + this.wkst = Recur.icalDayToNumericDay(this.wkst); + } + + if (this.until && !(this.until instanceof Time)) { + this.until = Time.fromString(this.until); + } + } + + /** + * The jCal representation of this recurrence type. + * @return {Object} + */ + toJSON() { + let res = Object.create(null); + res.freq = this.freq; + + if (this.count) { + res.count = this.count; + } + + if (this.interval > 1) { + res.interval = this.interval; + } + + for (let [k, kparts] of Object.entries(this.parts)) { + if (Array.isArray(kparts) && kparts.length == 1) { + res[k.toLowerCase()] = kparts[0]; + } else { + res[k.toLowerCase()] = clone(kparts); + } + } + + if (this.until) { + res.until = this.until.toString(); + } + if ('wkst' in this && this.wkst !== Time.DEFAULT_WEEK_START) { + res.wkst = Recur.numericDayToIcalDay(this.wkst); + } + return res; + } + + /** + * The string representation of this recurrence rule. + * @return {String} + */ + toString() { + // TODO retain order + let str = "FREQ=" + this.freq; + if (this.count) { + str += ";COUNT=" + this.count; + } + if (this.interval > 1) { + str += ";INTERVAL=" + this.interval; + } + for (let [k, v] of Object.entries(this.parts)) { + str += ";" + k + "=" + v; + } + if (this.until) { + str += ';UNTIL=' + this.until.toICALString(); + } + if ('wkst' in this && this.wkst !== Time.DEFAULT_WEEK_START) { + str += ';WKST=' + Recur.numericDayToIcalDay(this.wkst); + } + return str; + } +} +export default Recur; + +function parseNumericValue(type, min, max, value) { + let result = value; + + if (value[0] === '+') { + result = value.slice(1); + } + + result = strictParseInt(result); + + if (min !== undefined && value < min) { + throw new Error( + type + ': invalid value "' + value + '" must be > ' + min + ); + } + + if (max !== undefined && value > max) { + throw new Error( + type + ': invalid value "' + value + '" must be < ' + min + ); + } + + return result; +} + +const optionDesign = { + FREQ: function(value, dict, fmtIcal) { + // yes this is actually equal or faster then regex. + // upside here is we can enumerate the valid values. + if (ALLOWED_FREQ.indexOf(value) !== -1) { + dict.freq = value; + } else { + throw new Error( + 'invalid frequency "' + value + '" expected: "' + + ALLOWED_FREQ.join(', ') + '"' + ); + } + }, + + COUNT: function(value, dict, fmtIcal) { + dict.count = strictParseInt(value); + }, + + INTERVAL: function(value, dict, fmtIcal) { + dict.interval = strictParseInt(value); + if (dict.interval < 1) { + // 0 or negative values are not allowed, some engines seem to generate + // it though. Assume 1 instead. + dict.interval = 1; + } + }, + + UNTIL: function(value, dict, fmtIcal) { + if (value.length > 10) { + dict.until = design.icalendar.value['date-time'].fromICAL(value); + } else { + dict.until = design.icalendar.value.date.fromICAL(value); + } + if (!fmtIcal) { + dict.until = Time.fromString(dict.until); + } + }, + + WKST: function(value, dict, fmtIcal) { + if (VALID_DAY_NAMES.test(value)) { + dict.wkst = Recur.icalDayToNumericDay(value); + } else { + throw new Error('invalid WKST value "' + value + '"'); + } + } +}; + +const partDesign = { + BYSECOND: parseNumericValue.bind(undefined, 'BYSECOND', 0, 60), + BYMINUTE: parseNumericValue.bind(undefined, 'BYMINUTE', 0, 59), + BYHOUR: parseNumericValue.bind(undefined, 'BYHOUR', 0, 23), + BYDAY: function(value) { + if (VALID_BYDAY_PART.test(value)) { + return value; + } else { + throw new Error('invalid BYDAY value "' + value + '"'); + } + }, + BYMONTHDAY: parseNumericValue.bind(undefined, 'BYMONTHDAY', -31, 31), + BYYEARDAY: parseNumericValue.bind(undefined, 'BYYEARDAY', -366, 366), + BYWEEKNO: parseNumericValue.bind(undefined, 'BYWEEKNO', -53, 53), + BYMONTH: parseNumericValue.bind(undefined, 'BYMONTH', 1, 12), + BYSETPOS: parseNumericValue.bind(undefined, 'BYSETPOS', -366, 366) +}; diff --git a/lib/ical/recur_expansion.js b/lib/ical/recur_expansion.js new file mode 100644 index 0000000..7c2db67 --- /dev/null +++ b/lib/ical/recur_expansion.js @@ -0,0 +1,475 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */ + +import Time from "./time.js"; +import RecurIterator from "./recur_iterator.js"; +// needed for typescript type resolution +// eslint-disable-next-line no-unused-vars +import Component from "./component.js"; +import { formatClassType, binsearchInsert } from "./helpers.js"; + +/** + * Primary class for expanding recurring rules. Can take multiple rrules, rdates, exdate(s) and + * iterate (in order) over each next occurrence. + * + * Once initialized this class can also be serialized saved and continue iteration from the last + * point. + * + * NOTE: it is intended that this class is to be used with {@link ICAL.Event} which handles recurrence + * exceptions. + * + * @example + * // assuming event is a parsed ical component + * var event; + * + * var expand = new ICAL.RecurExpansion({ + * component: event, + * dtstart: event.getFirstPropertyValue('dtstart') + * }); + * + * // remember there are infinite rules so it is a good idea to limit the scope of the iterations + * // then resume later on. + * + * // next is always an ICAL.Time or null + * var next; + * + * while (someCondition && (next = expand.next())) { + * // do something with next + * } + * + * // save instance for later + * var json = JSON.stringify(expand); + * + * //... + * + * // NOTE: if the component's properties have changed you will need to rebuild the class and start + * // over. This only works when the component's recurrence info is the same. + * var expand = new ICAL.RecurExpansion(JSON.parse(json)); + * + * @memberof ICAL + */ +class RecurExpansion { + /** + * Creates a new ICAL.RecurExpansion instance. + * + * The options object can be filled with the specified initial values. It can also contain + * additional members, as a result of serializing a previous expansion state, as shown in the + * example. + * + * @param {Object} options + * Recurrence expansion options + * @param {Time} options.dtstart + * Start time of the event + * @param {Component=} options.component + * Component for expansion, required if not resuming. + */ + constructor(options) { + this.ruleDates = []; + this.exDates = []; + this.fromData(options); + } + + /** + * True when iteration is fully completed. + * @type {Boolean} + */ + complete = false; + + /** + * Array of rrule iterators. + * + * @type {RecurIterator[]} + * @private + */ + ruleIterators = null; + + /** + * Array of rdate instances. + * + * @type {Time[]} + * @private + */ + ruleDates = null; + + /** + * Array of exdate instances. + * + * @type {Time[]} + * @private + */ + exDates = null; + + /** + * Current position in ruleDates array. + * @type {Number} + * @private + */ + ruleDateInc = 0; + + /** + * Current position in exDates array + * @type {Number} + * @private + */ + exDateInc = 0; + + /** + * Current negative date. + * + * @type {Time} + * @private + */ + exDate = null; + + /** + * Current additional date. + * + * @type {Time} + * @private + */ + ruleDate = null; + + /** + * Start date of recurring rules. + * + * @type {Time} + */ + dtstart = null; + + /** + * Last expanded time + * + * @type {Time} + */ + last = null; + + /** + * Initialize the recurrence expansion from the data object. The options + * object may also contain additional members, see the + * {@link ICAL.RecurExpansion constructor} for more details. + * + * @param {Object} options + * Recurrence expansion options + * @param {Time} options.dtstart + * Start time of the event + * @param {Component=} options.component + * Component for expansion, required if not resuming. + */ + fromData(options) { + let start = formatClassType(options.dtstart, Time); + + if (!start) { + throw new Error('.dtstart (ICAL.Time) must be given'); + } else { + this.dtstart = start; + } + + if (options.component) { + this._init(options.component); + } else { + this.last = formatClassType(options.last, Time) || start.clone(); + + if (!options.ruleIterators) { + throw new Error('.ruleIterators or .component must be given'); + } + + this.ruleIterators = options.ruleIterators.map(function(item) { + return formatClassType(item, RecurIterator); + }); + + this.ruleDateInc = options.ruleDateInc; + this.exDateInc = options.exDateInc; + + if (options.ruleDates) { + this.ruleDates = options.ruleDates.map(item => formatClassType(item, Time)); + this.ruleDate = this.ruleDates[this.ruleDateInc]; + } + + if (options.exDates) { + this.exDates = options.exDates.map(item => formatClassType(item, Time)); + this.exDate = this.exDates[this.exDateInc]; + } + + if (typeof(options.complete) !== 'undefined') { + this.complete = options.complete; + } + } + } + + /** + * Retrieve the next occurrence in the series. + * @return {Time} + */ + next() { + let iter; + let next; + let compare; + + let maxTries = 500; + let currentTry = 0; + + while (true) { + if (currentTry++ > maxTries) { + throw new Error( + 'max tries have occurred, rule may be impossible to fulfill.' + ); + } + + next = this.ruleDate; + iter = this._nextRecurrenceIter(this.last); + + // no more matches + // because we increment the rule day or rule + // _after_ we choose a value this should be + // the only spot where we need to worry about the + // end of events. + if (!next && !iter) { + // there are no more iterators or rdates + this.complete = true; + break; + } + + // no next rule day or recurrence rule is first. + if (!next || (iter && next.compare(iter.last) > 0)) { + // must be cloned, recur will reuse the time element. + next = iter.last.clone(); + // move to next so we can continue + iter.next(); + } + + // if the ruleDate is still next increment it. + if (this.ruleDate === next) { + this._nextRuleDay(); + } + + this.last = next; + + // check the negative rules + if (this.exDate) { + compare = this.exDate.compare(this.last); + + if (compare < 0) { + this._nextExDay(); + } + + // if the current rule is excluded skip it. + if (compare === 0) { + this._nextExDay(); + continue; + } + } + + //XXX: The spec states that after we resolve the final + // list of dates we execute exdate this seems somewhat counter + // intuitive to what I have seen most servers do so for now + // I exclude based on the original date not the one that may + // have been modified by the exception. + return this.last; + } + } + + /** + * Converts object into a serialize-able format. This format can be passed + * back into the expansion to resume iteration. + * @return {Object} + */ + toJSON() { + function toJSON(item) { + return item.toJSON(); + } + + let result = Object.create(null); + result.ruleIterators = this.ruleIterators.map(toJSON); + + if (this.ruleDates) { + result.ruleDates = this.ruleDates.map(toJSON); + } + + if (this.exDates) { + result.exDates = this.exDates.map(toJSON); + } + + result.ruleDateInc = this.ruleDateInc; + result.exDateInc = this.exDateInc; + result.last = this.last.toJSON(); + result.dtstart = this.dtstart.toJSON(); + result.complete = this.complete; + + return result; + } + + /** + * Extract all dates from the properties in the given component. The + * properties will be filtered by the property name. + * + * @private + * @param {Component} component The component to search in + * @param {String} propertyName The property name to search for + * @return {Time[]} The extracted dates. + */ + _extractDates(component, propertyName) { + let result = []; + let props = component.getAllProperties(propertyName); + + for (let i = 0, len = props.length; i < len; i++) { + for (let prop of props[i].getValues()) { + let idx = binsearchInsert( + result, + prop, + (a, b) => a.compare(b) + ); + + // ordered insert + result.splice(idx, 0, prop); + } + } + + return result; + } + + /** + * Initialize the recurrence expansion. + * + * @private + * @param {Component} component The component to initialize from. + */ + _init(component) { + this.ruleIterators = []; + + this.last = this.dtstart.clone(); + + // to provide api consistency non-recurring + // events can also use the iterator though it will + // only return a single time. + if (!component.hasProperty('rdate') && + !component.hasProperty('rrule') && + !component.hasProperty('recurrence-id')) { + this.ruleDate = this.last.clone(); + this.complete = true; + return; + } + + if (component.hasProperty('rdate')) { + this.ruleDates = this._extractDates(component, 'rdate'); + + // special hack for cases where first rdate is prior + // to the start date. We only check for the first rdate. + // This is mostly for google's crazy recurring date logic + // (contacts birthdays). + if ((this.ruleDates[0]) && + (this.ruleDates[0].compare(this.dtstart) < 0)) { + + this.ruleDateInc = 0; + this.last = this.ruleDates[0].clone(); + } else { + this.ruleDateInc = binsearchInsert( + this.ruleDates, + this.last, + (a, b) => a.compare(b) + ); + } + + this.ruleDate = this.ruleDates[this.ruleDateInc]; + } + + if (component.hasProperty('rrule')) { + let rules = component.getAllProperties('rrule'); + let i = 0; + let len = rules.length; + + let rule; + let iter; + + for (; i < len; i++) { + rule = rules[i].getFirstValue(); + iter = rule.iterator(this.dtstart); + this.ruleIterators.push(iter); + + // increment to the next occurrence so future + // calls to next return times beyond the initial iteration. + // XXX: I find this suspicious might be a bug? + iter.next(); + } + } + + if (component.hasProperty('exdate')) { + this.exDates = this._extractDates(component, 'exdate'); + // if we have a .last day we increment the index to beyond it. + this.exDateInc = binsearchInsert( + this.exDates, + this.last, + (a, b) => a.compare(b) + ); + + this.exDate = this.exDates[this.exDateInc]; + } + } + + /** + * Advance to the next exdate + * @private + */ + _nextExDay() { + this.exDate = this.exDates[++this.exDateInc]; + } + + /** + * Advance to the next rule date + * @private + */ + _nextRuleDay() { + this.ruleDate = this.ruleDates[++this.ruleDateInc]; + } + + /** + * Find and return the recurrence rule with the most recent event and + * return it. + * + * @private + * @return {?RecurIterator} Found iterator. + */ + _nextRecurrenceIter() { + let iters = this.ruleIterators; + + if (iters.length === 0) { + return null; + } + + let len = iters.length; + let iter; + let iterTime; + let iterIdx = 0; + let chosenIter; + + // loop through each iterator + for (; iterIdx < len; iterIdx++) { + iter = iters[iterIdx]; + iterTime = iter.last; + + // if iteration is complete + // then we must exclude it from + // the search and remove it. + if (iter.completed) { + len--; + if (iterIdx !== 0) { + iterIdx--; + } + iters.splice(iterIdx, 1); + continue; + } + + // find the most recent possible choice + if (!chosenIter || chosenIter.last.compare(iterTime) > 0) { + // that iterator is saved + chosenIter = iter; + } + } + + // the chosen iterator is returned but not mutated + // this iterator contains the most recent event. + return chosenIter; + } +} +export default RecurExpansion; diff --git a/lib/ical/recur_iterator.js b/lib/ical/recur_iterator.js new file mode 100644 index 0000000..8758c0a --- /dev/null +++ b/lib/ical/recur_iterator.js @@ -0,0 +1,1441 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */ + +import { formatClassType, clone, trunc } from "./helpers.js"; +import Recur from "./recur.js"; +import Time from "./time.js"; + +/** + * This lets typescript resolve our custom types in the + * generated d.ts files (jsdoc typedefs are converted to typescript types). + * Ignore prevents the typedefs from being documented more than once. + * + * @ignore + * @typedef {import("./types.js").weekDay} weekDay + * Imports the 'weekDay' type from the "types.js" module + */ + +/** + * An iterator for a single recurrence rule. This class usually doesn't have to be instanciated + * directly, the convenience method {@link ICAL.Recur#iterator} can be used. + * + * @memberof ICAL + */ +class RecurIterator { + static _indexMap = { + "BYSECOND": 0, + "BYMINUTE": 1, + "BYHOUR": 2, + "BYDAY": 3, + "BYMONTHDAY": 4, + "BYYEARDAY": 5, + "BYWEEKNO": 6, + "BYMONTH": 7, + "BYSETPOS": 8 + }; + + static _expandMap = { + "SECONDLY": [1, 1, 1, 1, 1, 1, 1, 1], + "MINUTELY": [2, 1, 1, 1, 1, 1, 1, 1], + "HOURLY": [2, 2, 1, 1, 1, 1, 1, 1], + "DAILY": [2, 2, 2, 1, 1, 1, 1, 1], + "WEEKLY": [2, 2, 2, 2, 3, 3, 1, 1], + "MONTHLY": [2, 2, 2, 2, 2, 3, 3, 1], + "YEARLY": [2, 2, 2, 2, 2, 2, 2, 2] + }; + + static UNKNOWN = 0; + static CONTRACT = 1; + static EXPAND = 2; + static ILLEGAL = 3; + + /** + * Creates a new ICAL.RecurIterator instance. The options object may contain additional members + * when resuming iteration from a previous run. + * + * @param {Object} options The iterator options + * @param {Recur} options.rule The rule to iterate. + * @param {Time} options.dtstart The start date of the event. + * @param {Boolean=} options.initialized When true, assume that options are + * from a previously constructed iterator. Initialization will not be + * repeated. + */ + constructor(options) { + this.fromData(options); + } + + /** + * True when iteration is finished. + * @type {Boolean} + */ + completed = false; + + /** + * The rule that is being iterated + * @type {Recur} + */ + rule = null; + + /** + * The start date of the event being iterated. + * @type {Time} + */ + dtstart = null; + + /** + * The last occurrence that was returned from the + * {@link RecurIterator#next} method. + * @type {Time} + */ + last = null; + + /** + * The sequence number from the occurrence + * @type {Number} + */ + occurrence_number = 0; + + /** + * The indices used for the {@link ICAL.RecurIterator#by_data} object. + * @type {Object} + * @private + */ + by_indices = null; + + /** + * If true, the iterator has already been initialized + * @type {Boolean} + * @private + */ + initialized = false; + + /** + * The initializd by-data. + * @type {Object} + * @private + */ + by_data = null; + + /** + * The expanded yeardays + * @type {Array} + * @private + */ + days = null; + + /** + * The index in the {@link ICAL.RecurIterator#days} array. + * @type {Number} + * @private + */ + days_index = 0; + + /** + * Initialize the recurrence iterator from the passed data object. This + * method is usually not called directly, you can initialize the iterator + * through the constructor. + * + * @param {Object} options The iterator options + * @param {Recur} options.rule The rule to iterate. + * @param {Time} options.dtstart The start date of the event. + * @param {Boolean=} options.initialized When true, assume that options are + * from a previously constructed iterator. Initialization will not be + * repeated. + */ + fromData(options) { + this.rule = formatClassType(options.rule, Recur); + + if (!this.rule) { + throw new Error('iterator requires a (ICAL.Recur) rule'); + } + + this.dtstart = formatClassType(options.dtstart, Time); + + if (!this.dtstart) { + throw new Error('iterator requires a (ICAL.Time) dtstart'); + } + + if (options.by_data) { + this.by_data = options.by_data; + } else { + this.by_data = clone(this.rule.parts, true); + } + + if (options.occurrence_number) + this.occurrence_number = options.occurrence_number; + + this.days = options.days || []; + if (options.last) { + this.last = formatClassType(options.last, Time); + } + + this.by_indices = options.by_indices; + + if (!this.by_indices) { + this.by_indices = { + "BYSECOND": 0, + "BYMINUTE": 0, + "BYHOUR": 0, + "BYDAY": 0, + "BYMONTH": 0, + "BYWEEKNO": 0, + "BYMONTHDAY": 0 + }; + } + + this.initialized = options.initialized || false; + + if (!this.initialized) { + try { + this.init(); + } catch (e) { + if (e instanceof InvalidRecurrenceRuleError) { + // Init may error if there are no possible recurrence instances from + // the rule, but we don't want to bubble this error up. Instead, we + // create an empty iterator. + this.completed = true; + } else { + // Propagate other errors to consumers. + throw e; + } + } + } + } + + /** + * Initialize the iterator + * @private + */ + init() { + this.initialized = true; + this.last = this.dtstart.clone(); + let parts = this.by_data; + + if ("BYDAY" in parts) { + // libical does this earlier when the rule is loaded, but we postpone to + // now so we can preserve the original order. + this.sort_byday_rules(parts.BYDAY); + } + + // If the BYYEARDAY appares, no other date rule part may appear + if ("BYYEARDAY" in parts) { + if ("BYMONTH" in parts || "BYWEEKNO" in parts || + "BYMONTHDAY" in parts || "BYDAY" in parts) { + throw new Error("Invalid BYYEARDAY rule"); + } + } + + // BYWEEKNO and BYMONTHDAY rule parts may not both appear + if ("BYWEEKNO" in parts && "BYMONTHDAY" in parts) { + throw new Error("BYWEEKNO does not fit to BYMONTHDAY"); + } + + // For MONTHLY recurrences (FREQ=MONTHLY) neither BYYEARDAY nor + // BYWEEKNO may appear. + if (this.rule.freq == "MONTHLY" && + ("BYYEARDAY" in parts || "BYWEEKNO" in parts)) { + throw new Error("For MONTHLY recurrences neither BYYEARDAY nor BYWEEKNO may appear"); + } + + // For WEEKLY recurrences (FREQ=WEEKLY) neither BYMONTHDAY nor + // BYYEARDAY may appear. + if (this.rule.freq == "WEEKLY" && + ("BYYEARDAY" in parts || "BYMONTHDAY" in parts)) { + throw new Error("For WEEKLY recurrences neither BYMONTHDAY nor BYYEARDAY may appear"); + } + + // BYYEARDAY may only appear in YEARLY rules + if (this.rule.freq != "YEARLY" && "BYYEARDAY" in parts) { + throw new Error("BYYEARDAY may only appear in YEARLY rules"); + } + + this.last.second = this.setup_defaults("BYSECOND", "SECONDLY", this.dtstart.second); + this.last.minute = this.setup_defaults("BYMINUTE", "MINUTELY", this.dtstart.minute); + this.last.hour = this.setup_defaults("BYHOUR", "HOURLY", this.dtstart.hour); + this.last.day = this.setup_defaults("BYMONTHDAY", "DAILY", this.dtstart.day); + this.last.month = this.setup_defaults("BYMONTH", "MONTHLY", this.dtstart.month); + + if (this.rule.freq == "WEEKLY") { + if ("BYDAY" in parts) { + let [, dow] = this.ruleDayOfWeek(parts.BYDAY[0], this.rule.wkst); + let wkdy = dow - this.last.dayOfWeek(this.rule.wkst); + if ((this.last.dayOfWeek(this.rule.wkst) < dow && wkdy >= 0) || wkdy < 0) { + // Initial time is after first day of BYDAY data + this.last.day += wkdy; + } + } else { + let dayName = Recur.numericDayToIcalDay(this.dtstart.dayOfWeek()); + parts.BYDAY = [dayName]; + } + } + + if (this.rule.freq == "YEARLY") { + // Some yearly recurrence rules may be specific enough to not actually + // occur on a yearly basis, e.g. the 29th day of February or the fifth + // Monday of a given month. The standard isn't clear on the intended + // behavior in these cases, but `libical` at least will iterate until it + // finds a matching year. + // CAREFUL: Some rules may specify an occurrence that can never happen, + // e.g. the first Monday of April so long as it falls on the 15th + // through the 21st. Detecting these is non-trivial, so ensure that we + // stop iterating at some point. + const untilYear = this.rule.until ? this.rule.until.year : 20000; + while (this.last.year <= untilYear) { + this.expand_year_days(this.last.year); + if (this.days.length > 0) { + break; + } + this.increment_year(this.rule.interval); + } + + if (this.days.length == 0) { + throw new InvalidRecurrenceRuleError(); + } + + this._nextByYearDay(); + } + + if (this.rule.freq == "MONTHLY") { + if (this.has_by_data("BYDAY")) { + let tempLast = null; + let initLast = this.last.clone(); + let daysInMonth = Time.daysInMonth(this.last.month, this.last.year); + + // Check every weekday in BYDAY with relative dow and pos. + for (let bydow of this.by_data.BYDAY) { + this.last = initLast.clone(); + let [pos, dow] = this.ruleDayOfWeek(bydow); + let dayOfMonth = this.last.nthWeekDay(dow, pos); + + // If |pos| >= 6, the byday is invalid for a monthly rule. + if (pos >= 6 || pos <= -6) { + throw new Error("Malformed values in BYDAY part"); + } + + // If a Byday with pos=+/-5 is not in the current month it + // must be searched in the next months. + if (dayOfMonth > daysInMonth || dayOfMonth <= 0) { + // Skip if we have already found a "last" in this month. + if (tempLast && tempLast.month == initLast.month) { + continue; + } + while (dayOfMonth > daysInMonth || dayOfMonth <= 0) { + this.increment_month(); + daysInMonth = Time.daysInMonth(this.last.month, this.last.year); + dayOfMonth = this.last.nthWeekDay(dow, pos); + } + } + + this.last.day = dayOfMonth; + if (!tempLast || this.last.compare(tempLast) < 0) { + tempLast = this.last.clone(); + } + } + this.last = tempLast.clone(); + + //XXX: This feels like a hack, but we need to initialize + // the BYMONTHDAY case correctly and byDayAndMonthDay handles + // this case. It accepts a special flag which will avoid incrementing + // the initial value without the flag days that match the start time + // would be missed. + if (this.has_by_data('BYMONTHDAY')) { + this._byDayAndMonthDay(true); + } + + if (this.last.day > daysInMonth || this.last.day == 0) { + throw new Error("Malformed values in BYDAY part"); + } + } else if (this.has_by_data("BYMONTHDAY")) { + // Change the day value so that normalisation won't change the month. + this.last.day = 1; + + // Get a sorted list of days in the starting month that match the rule. + let normalized = this.normalizeByMonthDayRules( + this.last.year, + this.last.month, + this.rule.parts.BYMONTHDAY + ).filter(d => d >= this.last.day); + + if (normalized.length) { + // There's at least one valid day, use it. + this.last.day = normalized[0]; + this.by_data.BYMONTHDAY = normalized; + } else { + // There's no occurrence in this month, find the next valid month. + // The longest possible sequence of skipped months is February-April-June, + // so we might need to call next_month up to three times. + if (!this.next_month() && !this.next_month() && !this.next_month()) { + throw new Error("No possible occurrences"); + } + } + } + } + } + + /** + * Retrieve the next occurrence from the iterator. + * @return {Time} + */ + next(again = false) { + let before = (this.last ? this.last.clone() : null); + + if ((this.rule.count && this.occurrence_number >= this.rule.count) || + (this.rule.until && this.last.compare(this.rule.until) > 0)) { + this.completed = true; + } + + if (this.completed) { + return null; + } + + if (this.occurrence_number == 0 && this.last.compare(this.dtstart) >= 0) { + // First of all, give the instance that was initialized + this.occurrence_number++; + return this.last; + } + + let valid; + do { + valid = 1; + + switch (this.rule.freq) { + case "SECONDLY": + this.next_second(); + break; + case "MINUTELY": + this.next_minute(); + break; + case "HOURLY": + this.next_hour(); + break; + case "DAILY": + this.next_day(); + break; + case "WEEKLY": + this.next_week(); + break; + case "MONTHLY": + valid = this.next_month(); + break; + case "YEARLY": + this.next_year(); + break; + + default: + return null; + } + } while (!this.check_contracting_rules() || + this.last.compare(this.dtstart) < 0 || + !valid); + + if (this.last.compare(before) == 0) { + if (again) { + throw new Error("Same occurrence found twice, protecting you from death by recursion"); + } + this.next(true); + } + + if (this.rule.until && this.last.compare(this.rule.until) > 0) { + this.completed = true; + return null; + } else { + this.occurrence_number++; + return this.last; + } + } + + next_second() { + return this.next_generic("BYSECOND", "SECONDLY", "second", "minute"); + } + + increment_second(inc) { + return this.increment_generic(inc, "second", 60, "minute"); + } + + next_minute() { + return this.next_generic("BYMINUTE", "MINUTELY", + "minute", "hour", "next_second"); + } + + increment_minute(inc) { + return this.increment_generic(inc, "minute", 60, "hour"); + } + + next_hour() { + return this.next_generic("BYHOUR", "HOURLY", "hour", + "monthday", "next_minute"); + } + + increment_hour(inc) { + this.increment_generic(inc, "hour", 24, "monthday"); + } + + next_day() { + let this_freq = (this.rule.freq == "DAILY"); + + if (this.next_hour() == 0) { + return 0; + } + + if (this_freq) { + this.increment_monthday(this.rule.interval); + } else { + this.increment_monthday(1); + } + + return 0; + } + + next_week() { + let end_of_data = 0; + + if (this.next_weekday_by_week() == 0) { + return end_of_data; + } + + if (this.has_by_data("BYWEEKNO")) { + this.by_indices.BYWEEKNO++; + + if (this.by_indices.BYWEEKNO == this.by_data.BYWEEKNO.length) { + this.by_indices.BYWEEKNO = 0; + end_of_data = 1; + } + + // HACK should be first month of the year + this.last.month = 1; + this.last.day = 1; + + let week_no = this.by_data.BYWEEKNO[this.by_indices.BYWEEKNO]; + + this.last.day += 7 * week_no; + + if (end_of_data) { + this.increment_year(1); + } + } else { + // Jump to the next week + this.increment_monthday(7 * this.rule.interval); + } + + return end_of_data; + } + + /** + * Normalize each by day rule for a given year/month. + * Takes into account ordering and negative rules + * + * @private + * @param {Number} year Current year. + * @param {Number} month Current month. + * @param {Array} rules Array of rules. + * + * @return {Array} sorted and normalized rules. + * Negative rules will be expanded to their + * correct positive values for easier processing. + */ + normalizeByMonthDayRules(year, month, rules) { + let daysInMonth = Time.daysInMonth(month, year); + + // XXX: This is probably bad for performance to allocate + // a new array for each month we scan, if possible + // we should try to optimize this... + let newRules = []; + + let ruleIdx = 0; + let len = rules.length; + let rule; + + for (; ruleIdx < len; ruleIdx++) { + rule = parseInt(rules[ruleIdx], 10); + if (isNaN(rule)) { + throw new Error('Invalid BYMONTHDAY value'); + } + + // if this rule falls outside of given + // month discard it. + if (Math.abs(rule) > daysInMonth) { + continue; + } + + // negative case + if (rule < 0) { + // we add (not subtract it is a negative number) + // one from the rule because 1 === last day of month + rule = daysInMonth + (rule + 1); + } else if (rule === 0) { + // skip zero: it is invalid. + continue; + } + + // only add unique items... + if (newRules.indexOf(rule) === -1) { + newRules.push(rule); + } + + } + + // unique and sort + return newRules.sort(function(a, b) { return a - b; }); + } + + /** + * NOTES: + * We are given a list of dates in the month (BYMONTHDAY) (23, etc..) + * Also we are given a list of days (BYDAY) (MO, 2SU, etc..) when + * both conditions match a given date (this.last.day) iteration stops. + * + * @private + * @param {Boolean=} isInit When given true will not increment the + * current day (this.last). + */ + _byDayAndMonthDay(isInit) { + let byMonthDay; // setup in initMonth + let byDay = this.by_data.BYDAY; + + let date; + let dateIdx = 0; + let dateLen; // setup in initMonth + let dayLen = byDay.length; + + // we are not valid by default + let dataIsValid = 0; + + let daysInMonth; + let self = this; + // we need a copy of this, because a DateTime gets normalized + // automatically if the day is out of range. At some points we + // set the last day to 0 to start counting. + let lastDay = this.last.day; + + function initMonth() { + daysInMonth = Time.daysInMonth( + self.last.month, self.last.year + ); + + byMonthDay = self.normalizeByMonthDayRules( + self.last.year, + self.last.month, + self.by_data.BYMONTHDAY + ); + + dateLen = byMonthDay.length; + + // For the case of more than one occurrence in one month + // we have to be sure to start searching after the last + // found date or at the last BYMONTHDAY, unless we are + // initializing the iterator because in this case we have + // to consider the last found date too. + while (byMonthDay[dateIdx] <= lastDay && + !(isInit && byMonthDay[dateIdx] == lastDay) && + dateIdx < dateLen - 1) { + dateIdx++; + } + } + + function nextMonth() { + // since the day is incremented at the start + // of the loop below, we need to start at 0 + lastDay = 0; + self.increment_month(); + dateIdx = 0; + initMonth(); + } + + initMonth(); + + // should come after initMonth + if (isInit) { + lastDay -= 1; + } + + // Use a counter to avoid an infinite loop with malformed rules. + // Stop checking after 4 years so we consider also a leap year. + let monthsCounter = 48; + + while (!dataIsValid && monthsCounter) { + monthsCounter--; + // increment the current date. This is really + // important otherwise we may fall into the infinite + // loop trap. The initial date takes care of the case + // where the current date is the date we are looking + // for. + date = lastDay + 1; + + if (date > daysInMonth) { + nextMonth(); + continue; + } + + // find next date + let next = byMonthDay[dateIdx++]; + + // this logic is dependent on the BYMONTHDAYS + // being in order (which is done by #normalizeByMonthDayRules) + if (next >= date) { + // if the next month day is in the future jump to it. + lastDay = next; + } else { + // in this case the 'next' monthday has past + // we must move to the month. + nextMonth(); + continue; + } + + // Now we can loop through the day rules to see + // if one matches the current month date. + for (let dayIdx = 0; dayIdx < dayLen; dayIdx++) { + let parts = this.ruleDayOfWeek(byDay[dayIdx]); + let pos = parts[0]; + let dow = parts[1]; + + this.last.day = lastDay; + if (this.last.isNthWeekDay(dow, pos)) { + // when we find the valid one we can mark + // the conditions as met and break the loop. + // (Because we have this condition above + // it will also break the parent loop). + dataIsValid = 1; + break; + } + } + + // It is completely possible that the combination + // cannot be matched in the current month. + // When we reach the end of possible combinations + // in the current month we iterate to the next one. + // since dateIdx is incremented right after getting + // "next", we don't need dateLen -1 here. + if (!dataIsValid && dateIdx === dateLen) { + nextMonth(); + continue; + } + } + + if (monthsCounter <= 0) { + // Checked 4 years without finding a Byday that matches + // a Bymonthday. Maybe the rule is not correct. + throw new Error("Malformed values in BYDAY combined with BYMONTHDAY parts"); + } + + + return dataIsValid; + } + + next_month() { + let data_valid = 1; + + if (this.next_hour() == 0) { + return data_valid; + } + + if (this.has_by_data("BYDAY") && this.has_by_data("BYMONTHDAY")) { + data_valid = this._byDayAndMonthDay(); + } else if (this.has_by_data("BYDAY")) { + let daysInMonth = Time.daysInMonth(this.last.month, this.last.year); + let setpos = 0; + let setpos_total = 0; + + if (this.has_by_data("BYSETPOS")) { + let last_day = this.last.day; + for (let day = 1; day <= daysInMonth; day++) { + this.last.day = day; + if (this.is_day_in_byday(this.last)) { + setpos_total++; + if (day <= last_day) { + setpos++; + } + } + } + this.last.day = last_day; + } + + data_valid = 0; + let day; + for (day = this.last.day + 1; day <= daysInMonth; day++) { + this.last.day = day; + + if (this.is_day_in_byday(this.last)) { + if (!this.has_by_data("BYSETPOS") || + this.check_set_position(++setpos) || + this.check_set_position(setpos - setpos_total - 1)) { + + data_valid = 1; + break; + } + } + } + + if (day > daysInMonth) { + this.last.day = 1; + this.increment_month(); + + if (this.is_day_in_byday(this.last)) { + if (!this.has_by_data("BYSETPOS") || this.check_set_position(1)) { + data_valid = 1; + } + } else { + data_valid = 0; + } + } + } else if (this.has_by_data("BYMONTHDAY")) { + this.by_indices.BYMONTHDAY++; + + if (this.by_indices.BYMONTHDAY >= this.by_data.BYMONTHDAY.length) { + this.by_indices.BYMONTHDAY = 0; + this.increment_month(); + if (this.by_indices.BYMONTHDAY >= this.by_data.BYMONTHDAY.length) { + return 0; + } + } + + let daysInMonth = Time.daysInMonth(this.last.month, this.last.year); + let day = this.by_data.BYMONTHDAY[this.by_indices.BYMONTHDAY]; + + if (day < 0) { + day = daysInMonth + day + 1; + } + + if (day > daysInMonth) { + this.last.day = 1; + data_valid = this.is_day_in_byday(this.last); + } else { + this.last.day = day; + } + } else { + this.increment_month(); + let daysInMonth = Time.daysInMonth(this.last.month, this.last.year); + if (this.by_data.BYMONTHDAY[0] > daysInMonth) { + data_valid = 0; + } else { + this.last.day = this.by_data.BYMONTHDAY[0]; + } + } + + return data_valid; + } + + next_weekday_by_week() { + let end_of_data = 0; + + if (this.next_hour() == 0) { + return end_of_data; + } + + if (!this.has_by_data("BYDAY")) { + return 1; + } + + for (;;) { + let tt = new Time(); + this.by_indices.BYDAY++; + + if (this.by_indices.BYDAY == Object.keys(this.by_data.BYDAY).length) { + this.by_indices.BYDAY = 0; + end_of_data = 1; + } + + let coded_day = this.by_data.BYDAY[this.by_indices.BYDAY]; + let parts = this.ruleDayOfWeek(coded_day); + let dow = parts[1]; + + dow -= this.rule.wkst; + + if (dow < 0) { + dow += 7; + } + + tt.year = this.last.year; + tt.month = this.last.month; + tt.day = this.last.day; + + let startOfWeek = tt.startDoyWeek(this.rule.wkst); + + if (dow + startOfWeek < 1) { + // The selected date is in the previous year + if (!end_of_data) { + continue; + } + } + + let next = Time.fromDayOfYear(startOfWeek + dow, this.last.year); + + /** + * The normalization horrors below are due to + * the fact that when the year/month/day changes + * it can effect the other operations that come after. + */ + this.last.year = next.year; + this.last.month = next.month; + this.last.day = next.day; + + return end_of_data; + } + } + + next_year() { + if (this.next_hour() == 0) { + return 0; + } + + if (++this.days_index == this.days.length) { + this.days_index = 0; + do { + this.increment_year(this.rule.interval); + if (this.has_by_data("BYMONTHDAY")) { + this.by_data.BYMONTHDAY = this.normalizeByMonthDayRules( + this.last.year, + this.last.month, + this.rule.parts.BYMONTHDAY + ); + } + this.expand_year_days(this.last.year); + } while (this.days.length == 0); + } + + this._nextByYearDay(); + + return 1; + } + + _nextByYearDay() { + let doy = this.days[this.days_index]; + let year = this.last.year; + if (doy < 1) { + // Time.fromDayOfYear(doy, year) indexes relative to the + // start of the given year. That is different from the + // semantics of BYYEARDAY where negative indexes are an + // offset from the end of the given year. + doy += 1; + year += 1; + } + let next = Time.fromDayOfYear(doy, year); + this.last.day = next.day; + this.last.month = next.month; + } + + /** + * @param dow (eg: '1TU', '-1MO') + * @param {weekDay=} aWeekStart The week start weekday + * @return [pos, numericDow] (eg: [1, 3]) numericDow is relative to aWeekStart + */ + ruleDayOfWeek(dow, aWeekStart) { + let matches = dow.match(/([+-]?[0-9])?(MO|TU|WE|TH|FR|SA|SU)/); + if (matches) { + let pos = parseInt(matches[1] || 0, 10); + dow = Recur.icalDayToNumericDay(matches[2], aWeekStart); + return [pos, dow]; + } else { + return [0, 0]; + } + } + + next_generic(aRuleType, aInterval, aDateAttr, aFollowingAttr, aPreviousIncr) { + let has_by_rule = (aRuleType in this.by_data); + let this_freq = (this.rule.freq == aInterval); + let end_of_data = 0; + + if (aPreviousIncr && this[aPreviousIncr]() == 0) { + return end_of_data; + } + + if (has_by_rule) { + this.by_indices[aRuleType]++; + let dta = this.by_data[aRuleType]; + + if (this.by_indices[aRuleType] == dta.length) { + this.by_indices[aRuleType] = 0; + end_of_data = 1; + } + this.last[aDateAttr] = dta[this.by_indices[aRuleType]]; + } else if (this_freq) { + this["increment_" + aDateAttr](this.rule.interval); + } + + if (has_by_rule && end_of_data && this_freq) { + this["increment_" + aFollowingAttr](1); + } + + return end_of_data; + } + + increment_monthday(inc) { + for (let i = 0; i < inc; i++) { + let daysInMonth = Time.daysInMonth(this.last.month, this.last.year); + this.last.day++; + + if (this.last.day > daysInMonth) { + this.last.day -= daysInMonth; + this.increment_month(); + } + } + } + + increment_month() { + this.last.day = 1; + if (this.has_by_data("BYMONTH")) { + this.by_indices.BYMONTH++; + + if (this.by_indices.BYMONTH == this.by_data.BYMONTH.length) { + this.by_indices.BYMONTH = 0; + this.increment_year(1); + } + + this.last.month = this.by_data.BYMONTH[this.by_indices.BYMONTH]; + } else { + if (this.rule.freq == "MONTHLY") { + this.last.month += this.rule.interval; + } else { + this.last.month++; + } + + this.last.month--; + let years = trunc(this.last.month / 12); + this.last.month %= 12; + this.last.month++; + + if (years != 0) { + this.increment_year(years); + } + } + + if (this.has_by_data("BYMONTHDAY")) { + this.by_data.BYMONTHDAY = this.normalizeByMonthDayRules( + this.last.year, + this.last.month, + this.rule.parts.BYMONTHDAY + ); + } + } + + increment_year(inc) { + // Don't jump into the next month if this.last is Feb 29. + this.last.day = 1; + this.last.year += inc; + } + + increment_generic(inc, aDateAttr, aFactor, aNextIncrement) { + this.last[aDateAttr] += inc; + let nextunit = trunc(this.last[aDateAttr] / aFactor); + this.last[aDateAttr] %= aFactor; + if (nextunit != 0) { + this["increment_" + aNextIncrement](nextunit); + } + } + + has_by_data(aRuleType) { + return (aRuleType in this.rule.parts); + } + + expand_year_days(aYear) { + let t = new Time(); + this.days = []; + + // We need our own copy with a few keys set + let parts = {}; + let rules = ["BYDAY", "BYWEEKNO", "BYMONTHDAY", "BYMONTH", "BYYEARDAY"]; + for (let part of rules) { + if (part in this.rule.parts) { + parts[part] = this.rule.parts[part]; + } + } + + if ("BYMONTH" in parts && "BYWEEKNO" in parts) { + let valid = 1; + let validWeeks = {}; + t.year = aYear; + t.isDate = true; + + for (let monthIdx = 0; monthIdx < this.by_data.BYMONTH.length; monthIdx++) { + let month = this.by_data.BYMONTH[monthIdx]; + t.month = month; + t.day = 1; + let first_week = t.weekNumber(this.rule.wkst); + t.day = Time.daysInMonth(month, aYear); + let last_week = t.weekNumber(this.rule.wkst); + for (monthIdx = first_week; monthIdx < last_week; monthIdx++) { + validWeeks[monthIdx] = 1; + } + } + + for (let weekIdx = 0; weekIdx < this.by_data.BYWEEKNO.length && valid; weekIdx++) { + let weekno = this.by_data.BYWEEKNO[weekIdx]; + if (weekno < 52) { + valid &= validWeeks[weekIdx]; + } else { + valid = 0; + } + } + + if (valid) { + delete parts.BYMONTH; + } else { + delete parts.BYWEEKNO; + } + } + + let partCount = Object.keys(parts).length; + + if (partCount == 0) { + let t1 = this.dtstart.clone(); + t1.year = this.last.year; + this.days.push(t1.dayOfYear()); + } else if (partCount == 1 && "BYMONTH" in parts) { + for (let month of this.by_data.BYMONTH) { + let t2 = this.dtstart.clone(); + t2.year = aYear; + t2.month = month; + t2.isDate = true; + this.days.push(t2.dayOfYear()); + } + } else if (partCount == 1 && "BYMONTHDAY" in parts) { + for (let monthday of this.by_data.BYMONTHDAY) { + let t3 = this.dtstart.clone(); + if (monthday < 0) { + let daysInMonth = Time.daysInMonth(t3.month, aYear); + monthday = monthday + daysInMonth + 1; + } + t3.day = monthday; + t3.year = aYear; + t3.isDate = true; + this.days.push(t3.dayOfYear()); + } + } else if (partCount == 2 && + "BYMONTHDAY" in parts && + "BYMONTH" in parts) { + for (let month of this.by_data.BYMONTH) { + let daysInMonth = Time.daysInMonth(month, aYear); + for (let monthday of this.by_data.BYMONTHDAY) { + if (monthday < 0) { + monthday = monthday + daysInMonth + 1; + } + t.day = monthday; + t.month = month; + t.year = aYear; + t.isDate = true; + + this.days.push(t.dayOfYear()); + } + } + } else if (partCount == 1 && "BYWEEKNO" in parts) { + // TODO unimplemented in libical + } else if (partCount == 2 && + "BYWEEKNO" in parts && + "BYMONTHDAY" in parts) { + // TODO unimplemented in libical + } else if (partCount == 1 && "BYDAY" in parts) { + this.days = this.days.concat(this.expand_by_day(aYear)); + } else if (partCount == 2 && "BYDAY" in parts && "BYMONTH" in parts) { + for (let month of this.by_data.BYMONTH) { + let daysInMonth = Time.daysInMonth(month, aYear); + + t.year = aYear; + t.month = month; + t.day = 1; + t.isDate = true; + + let first_dow = t.dayOfWeek(); + let doy_offset = t.dayOfYear() - 1; + + t.day = daysInMonth; + let last_dow = t.dayOfWeek(); + + if (this.has_by_data("BYSETPOS")) { + let by_month_day = []; + for (let day = 1; day <= daysInMonth; day++) { + t.day = day; + if (this.is_day_in_byday(t)) { + by_month_day.push(day); + } + } + + for (let spIndex = 0; spIndex < by_month_day.length; spIndex++) { + if (this.check_set_position(spIndex + 1) || + this.check_set_position(spIndex - by_month_day.length)) { + this.days.push(doy_offset + by_month_day[spIndex]); + } + } + } else { + for (let coded_day of this.by_data.BYDAY) { + let bydayParts = this.ruleDayOfWeek(coded_day); + let pos = bydayParts[0]; + let dow = bydayParts[1]; + let month_day; + + let first_matching_day = ((dow + 7 - first_dow) % 7) + 1; + let last_matching_day = daysInMonth - ((last_dow + 7 - dow) % 7); + + if (pos == 0) { + for (let day = first_matching_day; day <= daysInMonth; day += 7) { + this.days.push(doy_offset + day); + } + } else if (pos > 0) { + month_day = first_matching_day + (pos - 1) * 7; + + if (month_day <= daysInMonth) { + this.days.push(doy_offset + month_day); + } + } else { + month_day = last_matching_day + (pos + 1) * 7; + + if (month_day > 0) { + this.days.push(doy_offset + month_day); + } + } + } + } + } + // Return dates in order of occurrence (1,2,3,...) instead + // of by groups of weekdays (1,8,15,...,2,9,16,...). + this.days.sort(function(a, b) { return a - b; }); // Comparator function allows to sort numbers. + } else if (partCount == 2 && "BYDAY" in parts && "BYMONTHDAY" in parts) { + let expandedDays = this.expand_by_day(aYear); + + for (let day of expandedDays) { + let tt = Time.fromDayOfYear(day, aYear); + if (this.by_data.BYMONTHDAY.indexOf(tt.day) >= 0) { + this.days.push(day); + } + } + } else if (partCount == 3 && + "BYDAY" in parts && + "BYMONTHDAY" in parts && + "BYMONTH" in parts) { + let expandedDays = this.expand_by_day(aYear); + + for (let day of expandedDays) { + let tt = Time.fromDayOfYear(day, aYear); + + if (this.by_data.BYMONTH.indexOf(tt.month) >= 0 && + this.by_data.BYMONTHDAY.indexOf(tt.day) >= 0) { + this.days.push(day); + } + } + } else if (partCount == 2 && "BYDAY" in parts && "BYWEEKNO" in parts) { + let expandedDays = this.expand_by_day(aYear); + + for (let day of expandedDays) { + let tt = Time.fromDayOfYear(day, aYear); + let weekno = tt.weekNumber(this.rule.wkst); + + if (this.by_data.BYWEEKNO.indexOf(weekno)) { + this.days.push(day); + } + } + } else if (partCount == 3 && + "BYDAY" in parts && + "BYWEEKNO" in parts && + "BYMONTHDAY" in parts) { + // TODO unimplemted in libical + } else if (partCount == 1 && "BYYEARDAY" in parts) { + this.days = this.days.concat(this.by_data.BYYEARDAY); + } else { + this.days = []; + } + + let daysInYear = Time.isLeapYear(aYear) ? 366 : 365; + this.days.sort((a, b) => { + if (a < 0) a += daysInYear + 1; + if (b < 0) b += daysInYear + 1; + return a - b; + }); + + return 0; + } + + expand_by_day(aYear) { + + let days_list = []; + let tmp = this.last.clone(); + + tmp.year = aYear; + tmp.month = 1; + tmp.day = 1; + tmp.isDate = true; + + let start_dow = tmp.dayOfWeek(); + + tmp.month = 12; + tmp.day = 31; + tmp.isDate = true; + + let end_dow = tmp.dayOfWeek(); + let end_year_day = tmp.dayOfYear(); + + for (let day of this.by_data.BYDAY) { + let parts = this.ruleDayOfWeek(day); + let pos = parts[0]; + let dow = parts[1]; + + if (pos == 0) { + let tmp_start_doy = ((dow + 7 - start_dow) % 7) + 1; + + for (let doy = tmp_start_doy; doy <= end_year_day; doy += 7) { + days_list.push(doy); + } + + } else if (pos > 0) { + let first; + if (dow >= start_dow) { + first = dow - start_dow + 1; + } else { + first = dow - start_dow + 8; + } + + days_list.push(first + (pos - 1) * 7); + } else { + let last; + pos = -pos; + + if (dow <= end_dow) { + last = end_year_day - end_dow + dow; + } else { + last = end_year_day - end_dow + dow - 7; + } + + days_list.push(last - (pos - 1) * 7); + } + } + return days_list; + } + + is_day_in_byday(tt) { + if (this.by_data.BYDAY) { + for (let day of this.by_data.BYDAY) { + let parts = this.ruleDayOfWeek(day); + let pos = parts[0]; + let dow = parts[1]; + let this_dow = tt.dayOfWeek(); + + if ((pos == 0 && dow == this_dow) || + (tt.nthWeekDay(dow, pos) == tt.day)) { + return 1; + } + } + } + + return 0; + } + + /** + * Checks if given value is in BYSETPOS. + * + * @private + * @param {Numeric} aPos position to check for. + * @return {Boolean} false unless BYSETPOS rules exist + * and the given value is present in rules. + */ + check_set_position(aPos) { + if (this.has_by_data('BYSETPOS')) { + let idx = this.by_data.BYSETPOS.indexOf(aPos); + // negative numbers are not false-y + return idx !== -1; + } + return false; + } + + sort_byday_rules(aRules) { + for (let i = 0; i < aRules.length; i++) { + for (let j = 0; j < i; j++) { + let one = this.ruleDayOfWeek(aRules[j], this.rule.wkst)[1]; + let two = this.ruleDayOfWeek(aRules[i], this.rule.wkst)[1]; + + if (one > two) { + let tmp = aRules[i]; + aRules[i] = aRules[j]; + aRules[j] = tmp; + } + } + } + } + + check_contract_restriction(aRuleType, v) { + let indexMapValue = RecurIterator._indexMap[aRuleType]; + let ruleMapValue = RecurIterator._expandMap[this.rule.freq][indexMapValue]; + let pass = false; + + if (aRuleType in this.by_data && + ruleMapValue == RecurIterator.CONTRACT) { + + let ruleType = this.by_data[aRuleType]; + + for (let bydata of ruleType) { + if (bydata == v) { + pass = true; + break; + } + } + } else { + // Not a contracting byrule or has no data, test passes + pass = true; + } + return pass; + } + + check_contracting_rules() { + let dow = this.last.dayOfWeek(); + let weekNo = this.last.weekNumber(this.rule.wkst); + let doy = this.last.dayOfYear(); + + return (this.check_contract_restriction("BYSECOND", this.last.second) && + this.check_contract_restriction("BYMINUTE", this.last.minute) && + this.check_contract_restriction("BYHOUR", this.last.hour) && + this.check_contract_restriction("BYDAY", Recur.numericDayToIcalDay(dow)) && + this.check_contract_restriction("BYWEEKNO", weekNo) && + this.check_contract_restriction("BYMONTHDAY", this.last.day) && + this.check_contract_restriction("BYMONTH", this.last.month) && + this.check_contract_restriction("BYYEARDAY", doy)); + } + + setup_defaults(aRuleType, req, deftime) { + let indexMapValue = RecurIterator._indexMap[aRuleType]; + let ruleMapValue = RecurIterator._expandMap[this.rule.freq][indexMapValue]; + + if (ruleMapValue != RecurIterator.CONTRACT) { + if (!(aRuleType in this.by_data)) { + this.by_data[aRuleType] = [deftime]; + } + if (this.rule.freq != req) { + return this.by_data[aRuleType][0]; + } + } + return deftime; + } + + /** + * Convert iterator into a serialize-able object. Will preserve current + * iteration sequence to ensure the seamless continuation of the recurrence + * rule. + * @return {Object} + */ + toJSON() { + let result = Object.create(null); + + result.initialized = this.initialized; + result.rule = this.rule.toJSON(); + result.dtstart = this.dtstart.toJSON(); + result.by_data = this.by_data; + result.days = this.days; + result.last = this.last.toJSON(); + result.by_indices = this.by_indices; + result.occurrence_number = this.occurrence_number; + + return result; + } +} + +/** + * An error indicating that a recurrence rule is invalid and produces no + * occurrences. + * + * @extends {Error} + * @class + */ +class InvalidRecurrenceRuleError extends Error { + constructor() { + super("Recurrence rule has no valid occurrences"); + } +} + +export default RecurIterator; diff --git a/lib/ical/stringify.js b/lib/ical/stringify.js new file mode 100644 index 0000000..410b625 --- /dev/null +++ b/lib/ical/stringify.js @@ -0,0 +1,302 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */ + +import design from "./design.js"; +import { foldline } from "./helpers.js"; + +/** + * This lets typescript resolve our custom types in the + * generated d.ts files (jsdoc typedefs are converted to typescript types). + * Ignore prevents the typedefs from being documented more than once. + * + * @ignore + * @typedef {import("./types.js").designSet} designSet + * Imports the 'designSet' type from the "types.js" module + */ + +const LINE_ENDING = '\r\n'; +const DEFAULT_VALUE_TYPE = 'unknown'; +const RFC6868_REPLACE_MAP = { '"': "^'", "\n": "^n", "^": "^^" }; + +/** + * Convert a full jCal/jCard array into a iCalendar/vCard string. + * + * @function ICAL.stringify + * @variation function + * @param {Array} jCal The jCal/jCard document + * @return {String} The stringified iCalendar/vCard document + */ +export default function stringify(jCal) { + if (typeof jCal[0] == "string") { + // This is a single component + jCal = [jCal]; + } + + let i = 0; + let len = jCal.length; + let result = ''; + + for (; i < len; i++) { + result += stringify.component(jCal[i]) + LINE_ENDING; + } + + return result; +} + +/** + * Converts an jCal component array into a ICAL string. + * Recursive will resolve sub-components. + * + * Exact component/property order is not saved all + * properties will come before subcomponents. + * + * @function ICAL.stringify.component + * @param {Array} component + * jCal/jCard fragment of a component + * @param {designSet} designSet + * The design data to use for this component + * @return {String} The iCalendar/vCard string + */ +stringify.component = function(component, designSet) { + let name = component[0].toUpperCase(); + let result = 'BEGIN:' + name + LINE_ENDING; + + let props = component[1]; + let propIdx = 0; + let propLen = props.length; + + let designSetName = component[0]; + // rfc6350 requires that in vCard 4.0 the first component is the VERSION + // component with as value 4.0, note that 3.0 does not have this requirement. + if (designSetName === 'vcard' && component[1].length > 0 && + !(component[1][0][0] === "version" && component[1][0][3] === "4.0")) { + designSetName = "vcard3"; + } + designSet = designSet || design.getDesignSet(designSetName); + + for (; propIdx < propLen; propIdx++) { + result += stringify.property(props[propIdx], designSet) + LINE_ENDING; + } + + // Ignore subcomponents if none exist, e.g. in vCard. + let comps = component[2] || []; + let compIdx = 0; + let compLen = comps.length; + + for (; compIdx < compLen; compIdx++) { + result += stringify.component(comps[compIdx], designSet) + LINE_ENDING; + } + + result += 'END:' + name; + return result; +}; + +/** + * Converts a single jCal/jCard property to a iCalendar/vCard string. + * + * @function ICAL.stringify.property + * @param {Array} property + * jCal/jCard property array + * @param {designSet} designSet + * The design data to use for this property + * @param {Boolean} noFold + * If true, the line is not folded + * @return {String} The iCalendar/vCard string + */ +stringify.property = function(property, designSet, noFold) { + let name = property[0].toUpperCase(); + let jsName = property[0]; + let params = property[1]; + + if (!designSet) { + designSet = design.defaultSet; + } + + let groupName = params.group; + let line; + if (designSet.propertyGroups && groupName) { + line = groupName.toUpperCase() + "." + name; + } else { + line = name; + } + + for (let [paramName, value] of Object.entries(params)) { + if (designSet.propertyGroups && paramName == 'group') { + continue; + } + + let paramDesign = designSet.param[paramName]; + let multiValue = paramDesign && paramDesign.multiValue; + if (multiValue && Array.isArray(value)) { + value = value.map(function(val) { + val = stringify._rfc6868Unescape(val); + val = stringify.paramPropertyValue(val, paramDesign.multiValueSeparateDQuote); + return val; + }); + value = stringify.multiValue(value, multiValue, "unknown", null, designSet); + } else { + value = stringify._rfc6868Unescape(value); + value = stringify.paramPropertyValue(value); + } + + line += ';' + paramName.toUpperCase() + '=' + value; + } + + if (property.length === 3) { + // If there are no values, we must assume a blank value + return line + ':'; + } + + let valueType = property[2]; + + let propDetails; + let multiValue = false; + let structuredValue = false; + let isDefault = false; + + if (jsName in designSet.property) { + propDetails = designSet.property[jsName]; + + if ('multiValue' in propDetails) { + multiValue = propDetails.multiValue; + } + + if (('structuredValue' in propDetails) && Array.isArray(property[3])) { + structuredValue = propDetails.structuredValue; + } + + if ('defaultType' in propDetails) { + if (valueType === propDetails.defaultType) { + isDefault = true; + } + } else { + if (valueType === DEFAULT_VALUE_TYPE) { + isDefault = true; + } + } + } else { + if (valueType === DEFAULT_VALUE_TYPE) { + isDefault = true; + } + } + + // push the VALUE property if type is not the default + // for the current property. + if (!isDefault) { + // value will never contain ;/:/, so we don't escape it here. + line += ';VALUE=' + valueType.toUpperCase(); + } + + line += ':'; + + if (multiValue && structuredValue) { + line += stringify.multiValue( + property[3], structuredValue, valueType, multiValue, designSet, structuredValue + ); + } else if (multiValue) { + line += stringify.multiValue( + property.slice(3), multiValue, valueType, null, designSet, false + ); + } else if (structuredValue) { + line += stringify.multiValue( + property[3], structuredValue, valueType, null, designSet, structuredValue + ); + } else { + line += stringify.value(property[3], valueType, designSet, false); + } + + return noFold ? line : foldline(line); +}; + +/** + * Handles escaping of property values that may contain: + * + * COLON (:), SEMICOLON (;), or COMMA (,) + * + * If any of the above are present the result is wrapped + * in double quotes. + * + * @function ICAL.stringify.paramPropertyValue + * @param {String} value Raw property value + * @param {boolean} force If value should be escaped even when unnecessary + * @return {String} Given or escaped value when needed + */ +stringify.paramPropertyValue = function(value, force) { + if (!force && + (value.indexOf(',') === -1) && + (value.indexOf(':') === -1) && + (value.indexOf(';') === -1)) { + + return value; + } + + return '"' + value + '"'; +}; + +/** + * Converts an array of ical values into a single + * string based on a type and a delimiter value (like ","). + * + * @function ICAL.stringify.multiValue + * @param {Array} values List of values to convert + * @param {String} delim Used to join the values (",", ";", ":") + * @param {String} type Lowecase ical value type + * (like boolean, date-time, etc..) + * @param {?String} innerMulti If set, each value will again be processed + * Used for structured values + * @param {designSet} designSet + * The design data to use for this property + * + * @return {String} iCalendar/vCard string for value + */ +stringify.multiValue = function(values, delim, type, innerMulti, designSet, structuredValue) { + let result = ''; + let len = values.length; + let i = 0; + + for (; i < len; i++) { + if (innerMulti && Array.isArray(values[i])) { + result += stringify.multiValue(values[i], innerMulti, type, null, designSet, structuredValue); + } else { + result += stringify.value(values[i], type, designSet, structuredValue); + } + + if (i !== (len - 1)) { + result += delim; + } + } + + return result; +}; + +/** + * Processes a single ical value runs the associated "toICAL" method from the + * design value type if available to convert the value. + * + * @function ICAL.stringify.value + * @param {String|Number} value A formatted value + * @param {String} type Lowercase iCalendar/vCard value type + * (like boolean, date-time, etc..) + * @return {String} iCalendar/vCard value for single value + */ +stringify.value = function(value, type, designSet, structuredValue) { + if (type in designSet.value && 'toICAL' in designSet.value[type]) { + return designSet.value[type].toICAL(value, structuredValue); + } + return value; +}; + +/** + * Internal helper for rfc6868. Exposing this on ICAL.stringify so that + * hackers can disable the rfc6868 parsing if the really need to. + * + * @param {String} val The value to unescape + * @return {String} The escaped value + */ +stringify._rfc6868Unescape = function(val) { + return val.replace(/[\n^"]/g, function(x) { + return RFC6868_REPLACE_MAP[x]; + }); +}; diff --git a/lib/ical/time.js b/lib/ical/time.js new file mode 100644 index 0000000..cec661b --- /dev/null +++ b/lib/ical/time.js @@ -0,0 +1,1334 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */ + +import Timezone from "./timezone.js"; +import Duration from "./duration.js"; +import design from "./design.js"; +// needed for typescript type resolution +// eslint-disable-next-line no-unused-vars +import Property from "./property.js"; +import TimezoneService from "./timezone_service.js"; +import { strictParseInt, trunc, pad2 } from "./helpers.js"; + +/** + * This lets typescript resolve our custom types in the + * generated d.ts files (jsdoc typedefs are converted to typescript types). + * Ignore prevents the typedefs from being documented more than once. + * + * @ignore + * @typedef {import("./types.js").weekDay} weekDay + * Imports the 'weekDay' type from the "types.js" module + */ + +/** + * @classdesc + * iCalendar Time representation (similar to JS Date object). Fully + * independent of system (OS) timezone / time. Unlike JS Date, the month + * January is 1, not zero. + * + * @example + * var time = new ICAL.Time({ + * year: 2012, + * month: 10, + * day: 11 + * minute: 0, + * second: 0, + * isDate: false + * }); + * + * + * @memberof ICAL +*/ +class Time { + static _dowCache = {}; + static _wnCache = {}; + + /** + * Returns the days in the given month + * + * @param {Number} month The month to check + * @param {Number} year The year to check + * @return {Number} The number of days in the month + */ + static daysInMonth(month, year) { + let _daysInMonth = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + let days = 30; + + if (month < 1 || month > 12) return days; + + days = _daysInMonth[month]; + + if (month == 2) { + days += Time.isLeapYear(year); + } + + return days; + } + + /** + * Checks if the year is a leap year + * + * @param {Number} year The year to check + * @return {Boolean} True, if the year is a leap year + */ + static isLeapYear(year) { + if (year <= 1752) { + return ((year % 4) == 0); + } else { + return (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)); + } + } + + /** + * Create a new ICAL.Time from the day of year and year. The date is returned + * in floating timezone. + * + * @param {Number} aDayOfYear The day of year + * @param {Number} aYear The year to create the instance in + * @return {Time} The created instance with the calculated date + */ + static fromDayOfYear(aDayOfYear, aYear) { + let year = aYear; + let doy = aDayOfYear; + let tt = new Time(); + tt.auto_normalize = false; + let is_leap = (Time.isLeapYear(year) ? 1 : 0); + + if (doy < 1) { + year--; + is_leap = (Time.isLeapYear(year) ? 1 : 0); + doy += Time.daysInYearPassedMonth[is_leap][12]; + return Time.fromDayOfYear(doy, year); + } else if (doy > Time.daysInYearPassedMonth[is_leap][12]) { + is_leap = (Time.isLeapYear(year) ? 1 : 0); + doy -= Time.daysInYearPassedMonth[is_leap][12]; + year++; + return Time.fromDayOfYear(doy, year); + } + + tt.year = year; + tt.isDate = true; + + for (let month = 11; month >= 0; month--) { + if (doy > Time.daysInYearPassedMonth[is_leap][month]) { + tt.month = month + 1; + tt.day = doy - Time.daysInYearPassedMonth[is_leap][month]; + break; + } + } + + tt.auto_normalize = true; + return tt; + } + + /** + * Returns a new ICAL.Time instance from a date string, e.g 2015-01-02. + * + * @deprecated Use {@link ICAL.Time.fromDateString} instead + * @param {String} str The string to create from + * @return {Time} The date/time instance + */ + static fromStringv2(str) { + return new Time({ + year: parseInt(str.slice(0, 4), 10), + month: parseInt(str.slice(5, 7), 10), + day: parseInt(str.slice(8, 10), 10), + isDate: true + }); + } + + /** + * Returns a new ICAL.Time instance from a date string, e.g 2015-01-02. + * + * @param {String} aValue The string to create from + * @return {Time} The date/time instance + */ + static fromDateString(aValue) { + // Dates should have no timezone. + // Google likes to sometimes specify Z on dates + // we specifically ignore that to avoid issues. + + // YYYY-MM-DD + // 2012-10-10 + return new Time({ + year: strictParseInt(aValue.slice(0, 4)), + month: strictParseInt(aValue.slice(5, 7)), + day: strictParseInt(aValue.slice(8, 10)), + isDate: true + }); + } + + /** + * Returns a new ICAL.Time instance from a date-time string, e.g + * 2015-01-02T03:04:05. If a property is specified, the timezone is set up + * from the property's TZID parameter. + * + * @param {String} aValue The string to create from + * @param {Property=} prop The property the date belongs to + * @return {Time} The date/time instance + */ + static fromDateTimeString(aValue, prop) { + if (aValue.length < 19) { + throw new Error( + 'invalid date-time value: "' + aValue + '"' + ); + } + + let zone; + let zoneId; + + if (aValue[19] && aValue[19] === 'Z') { + zone = Timezone.utcTimezone; + } else if (prop) { + zoneId = prop.getParameter('tzid'); + + if (prop.parent) { + if (prop.parent.name === 'standard' || prop.parent.name === 'daylight') { + // Per RFC 5545 3.8.2.4 and 3.8.2.2, start/end date-times within + // these components MUST be specified in local time. + zone = Timezone.localTimezone; + } else if (zoneId) { + // If the desired time zone is defined within the component tree, + // fetch its definition and prefer that. + zone = prop.parent.getTimeZoneByID(zoneId); + } + } + } + + const timeData = { + year: strictParseInt(aValue.slice(0, 4)), + month: strictParseInt(aValue.slice(5, 7)), + day: strictParseInt(aValue.slice(8, 10)), + hour: strictParseInt(aValue.slice(11, 13)), + minute: strictParseInt(aValue.slice(14, 16)), + second: strictParseInt(aValue.slice(17, 19)), + }; + + // Although RFC 5545 requires that all TZIDs used within a file have a + // corresponding time zone definition, we may not be parsing the full file + // or we may be dealing with a non-compliant file; in either case, we can + // check our own time zone service for the TZID in a last-ditch effort. + if (zoneId && !zone) { + timeData.timezone = zoneId; + } + + // 2012-10-10T10:10:10(Z)? + return new Time(timeData, zone); + } + + /** + * Returns a new ICAL.Time instance from a date or date-time string, + * + * @param {String} aValue The string to create from + * @param {Property=} prop The property the date belongs to + * @return {Time} The date/time instance + */ + static fromString(aValue, aProperty) { + if (aValue.length > 10) { + return Time.fromDateTimeString(aValue, aProperty); + } else { + return Time.fromDateString(aValue); + } + } + + /** + * Creates a new ICAL.Time instance from the given Javascript Date. + * + * @param {?Date} aDate The Javascript Date to read, or null to reset + * @param {Boolean} [useUTC=false] If true, the UTC values of the date will be used + */ + static fromJSDate(aDate, useUTC) { + let tt = new Time(); + return tt.fromJSDate(aDate, useUTC); + } + + /** + * Creates a new ICAL.Time instance from the the passed data object. + * + * @param {Object} aData Time initialization + * @param {Number=} aData.year The year for this date + * @param {Number=} aData.month The month for this date + * @param {Number=} aData.day The day for this date + * @param {Number=} aData.hour The hour for this date + * @param {Number=} aData.minute The minute for this date + * @param {Number=} aData.second The second for this date + * @param {Boolean=} aData.isDate If true, the instance represents a date + * (as opposed to a date-time) + * @param {Timezone=} aZone Timezone this position occurs in + */ + static fromData = function fromData(aData, aZone) { + let t = new Time(); + return t.fromData(aData, aZone); + }; + + /** + * Creates a new ICAL.Time instance from the current moment. + * The instance is “floating” - has no timezone relation. + * To create an instance considering the time zone, call + * ICAL.Time.fromJSDate(new Date(), true) + * @return {Time} + */ + static now() { + return Time.fromJSDate(new Date(), false); + } + + /** + * Returns the date on which ISO week number 1 starts. + * + * @see Time#weekNumber + * @param {Number} aYear The year to search in + * @param {weekDay=} aWeekStart The week start weekday, used for calculation. + * @return {Time} The date on which week number 1 starts + */ + static weekOneStarts(aYear, aWeekStart) { + let t = Time.fromData({ + year: aYear, + month: 1, + day: 1, + isDate: true + }); + + let dow = t.dayOfWeek(); + let wkst = aWeekStart || Time.DEFAULT_WEEK_START; + if (dow > Time.THURSDAY) { + t.day += 7; + } + if (wkst > Time.THURSDAY) { + t.day -= 7; + } + + t.day -= dow - wkst; + + return t; + } + + /** + * Get the dominical letter for the given year. Letters range from A - G for + * common years, and AG to GF for leap years. + * + * @param {Number} yr The year to retrieve the letter for + * @return {String} The dominical letter. + */ + static getDominicalLetter(yr) { + let LTRS = "GFEDCBA"; + let dom = (yr + (yr / 4 | 0) + (yr / 400 | 0) - (yr / 100 | 0) - 1) % 7; + let isLeap = Time.isLeapYear(yr); + if (isLeap) { + return LTRS[(dom + 6) % 7] + LTRS[dom]; + } else { + return LTRS[dom]; + } + } + + static #epochTime = null; + /** + * January 1st, 1970 as an ICAL.Time. + * @type {Time} + * @constant + * @instance + */ + static get epochTime() { + if (!this.#epochTime) { + this.#epochTime = Time.fromData({ + year: 1970, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + isDate: false, + timezone: "Z" + }); + } + return this.#epochTime; + } + + static _cmp_attr(a, b, attr) { + if (a[attr] > b[attr]) return 1; + if (a[attr] < b[attr]) return -1; + return 0; + } + + /** + * The days that have passed in the year after a given month. The array has + * two members, one being an array of passed days for non-leap years, the + * other analog for leap years. + * @example + * var isLeapYear = ICAL.Time.isLeapYear(year); + * var passedDays = ICAL.Time.daysInYearPassedMonth[isLeapYear][month]; + * @type {Array.>} + */ + static daysInYearPassedMonth = [ + [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365], + [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366] + ]; + + static SUNDAY = 1; + static MONDAY = 2; + static TUESDAY = 3; + static WEDNESDAY = 4; + static THURSDAY = 5; + static FRIDAY = 6; + static SATURDAY = 7; + + /** + * The default weekday for the WKST part. + * @constant + * @default ICAL.Time.MONDAY + */ + static DEFAULT_WEEK_START = 2; // MONDAY + + /** + * Creates a new ICAL.Time instance. + * + * @param {Object} data Time initialization + * @param {Number=} data.year The year for this date + * @param {Number=} data.month The month for this date + * @param {Number=} data.day The day for this date + * @param {Number=} data.hour The hour for this date + * @param {Number=} data.minute The minute for this date + * @param {Number=} data.second The second for this date + * @param {Boolean=} data.isDate If true, the instance represents a date (as + * opposed to a date-time) + * @param {Timezone} zone timezone this position occurs in + */ + constructor(data, zone) { + this.wrappedJSObject = this; + let time = this._time = Object.create(null); + + /* time defaults */ + time.year = 0; + time.month = 1; + time.day = 1; + time.hour = 0; + time.minute = 0; + time.second = 0; + time.isDate = false; + + this.fromData(data, zone); + } + + /** + * The class identifier. + * @constant + * @type {String} + * @default "icaltime" + */ + icalclass = "icaltime"; + _cachedUnixTime = null; + + /** + * The type name, to be used in the jCal object. This value may change and + * is strictly defined by the {@link ICAL.Time#isDate isDate} member. + * @type {String} + * @default "date-time" + */ + get icaltype() { + return this.isDate ? 'date' : 'date-time'; + } + + /** + * The timezone for this time. + * @type {Timezone} + */ + zone = null; + + /** + * Internal uses to indicate that a change has been made and the next read + * operation must attempt to normalize the value (for example changing the + * day to 33). + * + * @type {Boolean} + * @private + */ + _pendingNormalization = false; + + /** + * Returns a clone of the time object. + * + * @return {Time} The cloned object + */ + clone() { + return new Time(this._time, this.zone); + } + + /** + * Reset the time instance to epoch time + */ + reset() { + this.fromData(Time.epochTime); + this.zone = Timezone.utcTimezone; + } + + /** + * Reset the time instance to the given date/time values. + * + * @param {Number} year The year to set + * @param {Number} month The month to set + * @param {Number} day The day to set + * @param {Number} hour The hour to set + * @param {Number} minute The minute to set + * @param {Number} second The second to set + * @param {Timezone} timezone The timezone to set + */ + resetTo(year, month, day, hour, minute, second, timezone) { + this.fromData({ + year: year, + month: month, + day: day, + hour: hour, + minute: minute, + second: second, + zone: timezone + }); + } + + /** + * Set up the current instance from the Javascript date value. + * + * @param {?Date} aDate The Javascript Date to read, or null to reset + * @param {Boolean} [useUTC=false] If true, the UTC values of the date will be used + */ + fromJSDate(aDate, useUTC) { + if (!aDate) { + this.reset(); + } else { + if (useUTC) { + this.zone = Timezone.utcTimezone; + this.year = aDate.getUTCFullYear(); + this.month = aDate.getUTCMonth() + 1; + this.day = aDate.getUTCDate(); + this.hour = aDate.getUTCHours(); + this.minute = aDate.getUTCMinutes(); + this.second = aDate.getUTCSeconds(); + } else { + this.zone = Timezone.localTimezone; + this.year = aDate.getFullYear(); + this.month = aDate.getMonth() + 1; + this.day = aDate.getDate(); + this.hour = aDate.getHours(); + this.minute = aDate.getMinutes(); + this.second = aDate.getSeconds(); + } + } + this._cachedUnixTime = null; + return this; + } + + /** + * Sets up the current instance using members from the passed data object. + * + * @param {Object} aData Time initialization + * @param {Number=} aData.year The year for this date + * @param {Number=} aData.month The month for this date + * @param {Number=} aData.day The day for this date + * @param {Number=} aData.hour The hour for this date + * @param {Number=} aData.minute The minute for this date + * @param {Number=} aData.second The second for this date + * @param {Boolean=} aData.isDate If true, the instance represents a date + * (as opposed to a date-time) + * @param {Timezone=} aZone Timezone this position occurs in + */ + fromData(aData, aZone) { + if (aData) { + for (let [key, value] of Object.entries(aData)) { + // ical type cannot be set + if (key === 'icaltype') continue; + this[key] = value; + } + } + + if (aZone) { + this.zone = aZone; + } + + if (aData && !("isDate" in aData)) { + this.isDate = !("hour" in aData); + } else if (aData && ("isDate" in aData)) { + this.isDate = aData.isDate; + } + + if (aData && "timezone" in aData) { + let zone = TimezoneService.get( + aData.timezone + ); + + this.zone = zone || Timezone.localTimezone; + } + + if (aData && "zone" in aData) { + this.zone = aData.zone; + } + + if (!this.zone) { + this.zone = Timezone.localTimezone; + } + + this._cachedUnixTime = null; + return this; + } + + /** + * Calculate the day of week. + * @param {weekDay=} aWeekStart + * The week start weekday, defaults to SUNDAY + * @return {weekDay} + */ + dayOfWeek(aWeekStart) { + let firstDow = aWeekStart || Time.SUNDAY; + let dowCacheKey = (this.year << 12) + (this.month << 8) + (this.day << 3) + firstDow; + if (dowCacheKey in Time._dowCache) { + return Time._dowCache[dowCacheKey]; + } + + // Using Zeller's algorithm + let q = this.day; + let m = this.month + (this.month < 3 ? 12 : 0); + let Y = this.year - (this.month < 3 ? 1 : 0); + + let h = (q + Y + trunc(((m + 1) * 26) / 10) + trunc(Y / 4)); + if (true /* gregorian */) { // eslint-disable-line no-constant-condition + h += trunc(Y / 100) * 6 + trunc(Y / 400); + } else { + /* c8 ignore next 2 */ + h += 5; + } + + // Normalize to 1 = wkst + h = ((h + 7 - firstDow) % 7) + 1; + Time._dowCache[dowCacheKey] = h; + return h; + } + + /** + * Calculate the day of year. + * @return {Number} + */ + dayOfYear() { + let is_leap = (Time.isLeapYear(this.year) ? 1 : 0); + let diypm = Time.daysInYearPassedMonth; + return diypm[is_leap][this.month - 1] + this.day; + } + + /** + * Returns a copy of the current date/time, rewound to the start of the + * week. The resulting ICAL.Time instance is of icaltype date, even if this + * is a date-time. + * + * @param {weekDay=} aWeekStart + * The week start weekday, defaults to SUNDAY + * @return {Time} The start of the week (cloned) + */ + startOfWeek(aWeekStart) { + let firstDow = aWeekStart || Time.SUNDAY; + let result = this.clone(); + result.day -= ((this.dayOfWeek() + 7 - firstDow) % 7); + result.isDate = true; + result.hour = 0; + result.minute = 0; + result.second = 0; + return result; + } + + /** + * Returns a copy of the current date/time, shifted to the end of the week. + * The resulting ICAL.Time instance is of icaltype date, even if this is a + * date-time. + * + * @param {weekDay=} aWeekStart + * The week start weekday, defaults to SUNDAY + * @return {Time} The end of the week (cloned) + */ + endOfWeek(aWeekStart) { + let firstDow = aWeekStart || Time.SUNDAY; + let result = this.clone(); + result.day += (7 - this.dayOfWeek() + firstDow - Time.SUNDAY) % 7; + result.isDate = true; + result.hour = 0; + result.minute = 0; + result.second = 0; + return result; + } + + /** + * Returns a copy of the current date/time, rewound to the start of the + * month. The resulting ICAL.Time instance is of icaltype date, even if + * this is a date-time. + * + * @return {Time} The start of the month (cloned) + */ + startOfMonth() { + let result = this.clone(); + result.day = 1; + result.isDate = true; + result.hour = 0; + result.minute = 0; + result.second = 0; + return result; + } + + /** + * Returns a copy of the current date/time, shifted to the end of the + * month. The resulting ICAL.Time instance is of icaltype date, even if + * this is a date-time. + * + * @return {Time} The end of the month (cloned) + */ + endOfMonth() { + let result = this.clone(); + result.day = Time.daysInMonth(result.month, result.year); + result.isDate = true; + result.hour = 0; + result.minute = 0; + result.second = 0; + return result; + } + + /** + * Returns a copy of the current date/time, rewound to the start of the + * year. The resulting ICAL.Time instance is of icaltype date, even if + * this is a date-time. + * + * @return {Time} The start of the year (cloned) + */ + startOfYear() { + let result = this.clone(); + result.day = 1; + result.month = 1; + result.isDate = true; + result.hour = 0; + result.minute = 0; + result.second = 0; + return result; + } + + /** + * Returns a copy of the current date/time, shifted to the end of the + * year. The resulting ICAL.Time instance is of icaltype date, even if + * this is a date-time. + * + * @return {Time} The end of the year (cloned) + */ + endOfYear() { + let result = this.clone(); + result.day = 31; + result.month = 12; + result.isDate = true; + result.hour = 0; + result.minute = 0; + result.second = 0; + return result; + } + + /** + * First calculates the start of the week, then returns the day of year for + * this date. If the day falls into the previous year, the day is zero or negative. + * + * @param {weekDay=} aFirstDayOfWeek + * The week start weekday, defaults to SUNDAY + * @return {Number} The calculated day of year + */ + startDoyWeek(aFirstDayOfWeek) { + let firstDow = aFirstDayOfWeek || Time.SUNDAY; + let delta = this.dayOfWeek() - firstDow; + if (delta < 0) delta += 7; + return this.dayOfYear() - delta; + } + + /** + * Get the dominical letter for the current year. Letters range from A - G + * for common years, and AG to GF for leap years. + * + * @param {Number} yr The year to retrieve the letter for + * @return {String} The dominical letter. + */ + getDominicalLetter() { + return Time.getDominicalLetter(this.year); + } + + /** + * Finds the nthWeekDay relative to the current month (not day). The + * returned value is a day relative the month that this month belongs to so + * 1 would indicate the first of the month and 40 would indicate a day in + * the following month. + * + * @param {Number} aDayOfWeek Day of the week see the day name constants + * @param {Number} aPos Nth occurrence of a given week day values + * of 1 and 0 both indicate the first weekday of that type. aPos may + * be either positive or negative + * + * @return {Number} numeric value indicating a day relative + * to the current month of this time object + */ + nthWeekDay(aDayOfWeek, aPos) { + let daysInMonth = Time.daysInMonth(this.month, this.year); + let weekday; + let pos = aPos; + + let start = 0; + + let otherDay = this.clone(); + + if (pos >= 0) { + otherDay.day = 1; + + // because 0 means no position has been given + // 1 and 0 indicate the same day. + if (pos != 0) { + // remove the extra numeric value + pos--; + } + + // set current start offset to current day. + start = otherDay.day; + + // find the current day of week + let startDow = otherDay.dayOfWeek(); + + // calculate the difference between current + // day of the week and desired day of the week + let offset = aDayOfWeek - startDow; + + + // if the offset goes into the past + // week we add 7 so it goes into the next + // week. We only want to go forward in time here. + if (offset < 0) + // this is really important otherwise we would + // end up with dates from in the past. + offset += 7; + + // add offset to start so start is the same + // day of the week as the desired day of week. + start += offset; + + // because we are going to add (and multiply) + // the numeric value of the day we subtract it + // from the start position so not to add it twice. + start -= aDayOfWeek; + + // set week day + weekday = aDayOfWeek; + } else { + + // then we set it to the last day in the current month + otherDay.day = daysInMonth; + + // find the ends weekday + let endDow = otherDay.dayOfWeek(); + + pos++; + + weekday = (endDow - aDayOfWeek); + + if (weekday < 0) { + weekday += 7; + } + + weekday = daysInMonth - weekday; + } + + weekday += pos * 7; + + return start + weekday; + } + + /** + * Checks if current time is the nth weekday, relative to the current + * month. Will always return false when rule resolves outside of current + * month. + * + * @param {weekDay} aDayOfWeek Day of week to check + * @param {Number} aPos Relative position + * @return {Boolean} True, if it is the nth weekday + */ + isNthWeekDay(aDayOfWeek, aPos) { + let dow = this.dayOfWeek(); + + if (aPos === 0 && dow === aDayOfWeek) { + return true; + } + + // get pos + let day = this.nthWeekDay(aDayOfWeek, aPos); + + if (day === this.day) { + return true; + } + + return false; + } + + /** + * Calculates the ISO 8601 week number. The first week of a year is the + * week that contains the first Thursday. The year can have 53 weeks, if + * January 1st is a Friday. + * + * Note there are regions where the first week of the year is the one that + * starts on January 1st, which may offset the week number. Also, if a + * different week start is specified, this will also affect the week + * number. + * + * @see Time.weekOneStarts + * @param {weekDay} aWeekStart The weekday the week starts with + * @return {Number} The ISO week number + */ + weekNumber(aWeekStart) { + let wnCacheKey = (this.year << 12) + (this.month << 8) + (this.day << 3) + aWeekStart; + if (wnCacheKey in Time._wnCache) { + return Time._wnCache[wnCacheKey]; + } + // This function courtesty of Julian Bucknall, published under the MIT license + // http://www.boyet.com/articles/publishedarticles/calculatingtheisoweeknumb.html + // plus some fixes to be able to use different week starts. + let week1; + + let dt = this.clone(); + dt.isDate = true; + let isoyear = this.year; + + if (dt.month == 12 && dt.day > 25) { + week1 = Time.weekOneStarts(isoyear + 1, aWeekStart); + if (dt.compare(week1) < 0) { + week1 = Time.weekOneStarts(isoyear, aWeekStart); + } else { + isoyear++; + } + } else { + week1 = Time.weekOneStarts(isoyear, aWeekStart); + if (dt.compare(week1) < 0) { + week1 = Time.weekOneStarts(--isoyear, aWeekStart); + } + } + + let daysBetween = (dt.subtractDate(week1).toSeconds() / 86400); + let answer = trunc(daysBetween / 7) + 1; + Time._wnCache[wnCacheKey] = answer; + return answer; + } + + /** + * Adds the duration to the current time. The instance is modified in + * place. + * + * @param {Duration} aDuration The duration to add + */ + addDuration(aDuration) { + let mult = (aDuration.isNegative ? -1 : 1); + + // because of the duration optimizations it is much + // more efficient to grab all the values up front + // then set them directly (which will avoid a normalization call). + // So we don't actually normalize until we need it. + let second = this.second; + let minute = this.minute; + let hour = this.hour; + let day = this.day; + + second += mult * aDuration.seconds; + minute += mult * aDuration.minutes; + hour += mult * aDuration.hours; + day += mult * aDuration.days; + day += mult * 7 * aDuration.weeks; + + this.second = second; + this.minute = minute; + this.hour = hour; + this.day = day; + + this._cachedUnixTime = null; + } + + /** + * Subtract the date details (_excluding_ timezone). Useful for finding + * the relative difference between two time objects excluding their + * timezone differences. + * + * @param {Time} aDate The date to subtract + * @return {Duration} The difference as a duration + */ + subtractDate(aDate) { + let unixTime = this.toUnixTime() + this.utcOffset(); + let other = aDate.toUnixTime() + aDate.utcOffset(); + return Duration.fromSeconds(unixTime - other); + } + + /** + * Subtract the date details, taking timezones into account. + * + * @param {Time} aDate The date to subtract + * @return {Duration} The difference in duration + */ + subtractDateTz(aDate) { + let unixTime = this.toUnixTime(); + let other = aDate.toUnixTime(); + return Duration.fromSeconds(unixTime - other); + } + + /** + * Compares the ICAL.Time instance with another one. + * + * @param {Duration} aOther The instance to compare with + * @return {Number} -1, 0 or 1 for less/equal/greater + */ + compare(other) { + let a = this.toUnixTime(); + let b = other.toUnixTime(); + + if (a > b) return 1; + if (b > a) return -1; + return 0; + } + + /** + * Compares only the date part of this instance with another one. + * + * @param {Duration} other The instance to compare with + * @param {Timezone} tz The timezone to compare in + * @return {Number} -1, 0 or 1 for less/equal/greater + */ + compareDateOnlyTz(other, tz) { + let a = this.convertToZone(tz); + let b = other.convertToZone(tz); + let rc = 0; + + if ((rc = Time._cmp_attr(a, b, "year")) != 0) return rc; + if ((rc = Time._cmp_attr(a, b, "month")) != 0) return rc; + if ((rc = Time._cmp_attr(a, b, "day")) != 0) return rc; + + return rc; + } + + /** + * Convert the instance into another timezone. The returned ICAL.Time + * instance is always a copy. + * + * @param {Timezone} zone The zone to convert to + * @return {Time} The copy, converted to the zone + */ + convertToZone(zone) { + let copy = this.clone(); + let zone_equals = (this.zone.tzid == zone.tzid); + + if (!this.isDate && !zone_equals) { + Timezone.convert_time(copy, this.zone, zone); + } + + copy.zone = zone; + return copy; + } + + /** + * Calculates the UTC offset of the current date/time in the timezone it is + * in. + * + * @return {Number} UTC offset in seconds + */ + utcOffset() { + if (this.zone == Timezone.localTimezone || + this.zone == Timezone.utcTimezone) { + return 0; + } else { + return this.zone.utcOffset(this); + } + } + + /** + * Returns an RFC 5545 compliant ical representation of this object. + * + * @return {String} ical date/date-time + */ + toICALString() { + let string = this.toString(); + + if (string.length > 10) { + return design.icalendar.value['date-time'].toICAL(string); + } else { + return design.icalendar.value.date.toICAL(string); + } + } + + /** + * The string representation of this date/time, in jCal form + * (including : and - separators). + * @return {String} + */ + toString() { + let result = this.year + '-' + + pad2(this.month) + '-' + + pad2(this.day); + + if (!this.isDate) { + result += 'T' + pad2(this.hour) + ':' + + pad2(this.minute) + ':' + + pad2(this.second); + + if (this.zone === Timezone.utcTimezone) { + result += 'Z'; + } + } + + return result; + } + + /** + * Converts the current instance to a Javascript date + * @return {Date} + */ + toJSDate() { + if (this.zone == Timezone.localTimezone) { + if (this.isDate) { + return new Date(this.year, this.month - 1, this.day); + } else { + return new Date(this.year, this.month - 1, this.day, + this.hour, this.minute, this.second, 0); + } + } else { + return new Date(this.toUnixTime() * 1000); + } + } + + _normalize() { + if (this._time.isDate) { + this._time.hour = 0; + this._time.minute = 0; + this._time.second = 0; + } + this.adjust(0, 0, 0, 0); + + return this; + } + + /** + * Adjust the date/time by the given offset + * + * @param {Number} aExtraDays The extra amount of days + * @param {Number} aExtraHours The extra amount of hours + * @param {Number} aExtraMinutes The extra amount of minutes + * @param {Number} aExtraSeconds The extra amount of seconds + * @param {Number=} aTime The time to adjust, defaults to the + * current instance. + */ + adjust(aExtraDays, aExtraHours, aExtraMinutes, aExtraSeconds, aTime) { + + let minutesOverflow, hoursOverflow, + daysOverflow = 0, yearsOverflow = 0; + + let second, minute, hour, day; + let daysInMonth; + + let time = aTime || this._time; + + if (!time.isDate) { + second = time.second + aExtraSeconds; + time.second = second % 60; + minutesOverflow = trunc(second / 60); + if (time.second < 0) { + time.second += 60; + minutesOverflow--; + } + + minute = time.minute + aExtraMinutes + minutesOverflow; + time.minute = minute % 60; + hoursOverflow = trunc(minute / 60); + if (time.minute < 0) { + time.minute += 60; + hoursOverflow--; + } + + hour = time.hour + aExtraHours + hoursOverflow; + + time.hour = hour % 24; + daysOverflow = trunc(hour / 24); + if (time.hour < 0) { + time.hour += 24; + daysOverflow--; + } + } + + + // Adjust month and year first, because we need to know what month the day + // is in before adjusting it. + if (time.month > 12) { + yearsOverflow = trunc((time.month - 1) / 12); + } else if (time.month < 1) { + yearsOverflow = trunc(time.month / 12) - 1; + } + + time.year += yearsOverflow; + time.month -= 12 * yearsOverflow; + + // Now take care of the days (and adjust month if needed) + day = time.day + aExtraDays + daysOverflow; + + if (day > 0) { + for (;;) { + daysInMonth = Time.daysInMonth(time.month, time.year); + if (day <= daysInMonth) { + break; + } + + time.month++; + if (time.month > 12) { + time.year++; + time.month = 1; + } + + day -= daysInMonth; + } + } else { + while (day <= 0) { + if (time.month == 1) { + time.year--; + time.month = 12; + } else { + time.month--; + } + + day += Time.daysInMonth(time.month, time.year); + } + } + + time.day = day; + + this._cachedUnixTime = null; + return this; + } + + /** + * Sets up the current instance from unix time, the number of seconds since + * January 1st, 1970. + * + * @param {Number} seconds The seconds to set up with + */ + fromUnixTime(seconds) { + this.zone = Timezone.utcTimezone; + // We could use `fromJSDate` here, but this is about twice as fast. + // We could also clone `epochTime` and use `adjust` for a more + // ical.js-centric approach, but this is about 100 times as fast. + let date = new Date(seconds * 1000); + this.year = date.getUTCFullYear(); + this.month = date.getUTCMonth() + 1; + this.day = date.getUTCDate(); + if (this._time.isDate) { + this.hour = 0; + this.minute = 0; + this.second = 0; + } else { + this.hour = date.getUTCHours(); + this.minute = date.getUTCMinutes(); + this.second = date.getUTCSeconds(); + } + + this._cachedUnixTime = null; + } + + /** + * Converts the current instance to seconds since January 1st 1970. + * + * @return {Number} Seconds since 1970 + */ + toUnixTime() { + if (this._cachedUnixTime !== null) { + return this._cachedUnixTime; + } + let offset = this.utcOffset(); + + // we use the offset trick to ensure + // that we are getting the actual UTC time + let ms = Date.UTC( + this.year, + this.month - 1, + this.day, + this.hour, + this.minute, + this.second - offset + ); + + // seconds + this._cachedUnixTime = ms / 1000; + return this._cachedUnixTime; + } + + /** + * Converts time to into Object which can be serialized then re-created + * using the constructor. + * + * @example + * // toJSON will automatically be called + * var json = JSON.stringify(mytime); + * + * var deserialized = JSON.parse(json); + * + * var time = new ICAL.Time(deserialized); + * + * @return {Object} + */ + toJSON() { + let copy = [ + 'year', + 'month', + 'day', + 'hour', + 'minute', + 'second', + 'isDate' + ]; + + let result = Object.create(null); + + let i = 0; + let len = copy.length; + let prop; + + for (; i < len; i++) { + prop = copy[i]; + result[prop] = this[prop]; + } + + if (this.zone) { + result.timezone = this.zone.tzid; + } + + return result; + } +} +export default Time; + +(function setupNormalizeAttributes() { + // This needs to run before any instances are created! + function defineAttr(attr) { + Object.defineProperty(Time.prototype, attr, { + get: function getTimeAttr() { + if (this._pendingNormalization) { + this._normalize(); + this._pendingNormalization = false; + } + + return this._time[attr]; + }, + set: function setTimeAttr(val) { + // Check if isDate will be set and if was not set to normalize date. + // This avoids losing days when seconds, minutes and hours are zeroed + // what normalize will do when time is a date. + if (attr === "isDate" && val && !this._time.isDate) { + this.adjust(0, 0, 0, 0); + } + this._cachedUnixTime = null; + this._pendingNormalization = true; + this._time[attr] = val; + } + }); + + } + + defineAttr("year"); + defineAttr("month"); + defineAttr("day"); + defineAttr("hour"); + defineAttr("minute"); + defineAttr("second"); + defineAttr("isDate"); +})(); diff --git a/lib/ical/timezone.js b/lib/ical/timezone.js new file mode 100644 index 0000000..a59c216 --- /dev/null +++ b/lib/ical/timezone.js @@ -0,0 +1,526 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */ + +import Time from "./time.js"; +import Component from "./component.js"; +import ICALParse from "./parse.js"; +import { clone, binsearchInsert } from "./helpers.js"; + +const OPTIONS = ["tzid", "location", "tznames", "latitude", "longitude"]; + +/** + * Timezone representation. + * + * @example + * var vcalendar; + * var timezoneComp = vcalendar.getFirstSubcomponent('vtimezone'); + * var tzid = timezoneComp.getFirstPropertyValue('tzid'); + * + * var timezone = new ICAL.Timezone({ + * component: timezoneComp, + * tzid + * }); + * + * @memberof ICAL + */ +class Timezone { + static _compare_change_fn(a, b) { + if (a.year < b.year) return -1; + else if (a.year > b.year) return 1; + + if (a.month < b.month) return -1; + else if (a.month > b.month) return 1; + + if (a.day < b.day) return -1; + else if (a.day > b.day) return 1; + + if (a.hour < b.hour) return -1; + else if (a.hour > b.hour) return 1; + + if (a.minute < b.minute) return -1; + else if (a.minute > b.minute) return 1; + + if (a.second < b.second) return -1; + else if (a.second > b.second) return 1; + + return 0; + } + + /** + * Convert the date/time from one zone to the next. + * + * @param {Time} tt The time to convert + * @param {Timezone} from_zone The source zone to convert from + * @param {Timezone} to_zone The target zone to convert to + * @return {Time} The converted date/time object + */ + static convert_time(tt, from_zone, to_zone) { + if (tt.isDate || + from_zone.tzid == to_zone.tzid || + from_zone == Timezone.localTimezone || + to_zone == Timezone.localTimezone) { + tt.zone = to_zone; + return tt; + } + + let utcOffset = from_zone.utcOffset(tt); + tt.adjust(0, 0, 0, - utcOffset); + + utcOffset = to_zone.utcOffset(tt); + tt.adjust(0, 0, 0, utcOffset); + + return null; + } + + /** + * Creates a new ICAL.Timezone instance from the passed data object. + * + * @param {Component|Object} aData options for class + * @param {String|Component} aData.component + * If aData is a simple object, then this member can be set to either a + * string containing the component data, or an already parsed + * ICAL.Component + * @param {String} aData.tzid The timezone identifier + * @param {String} aData.location The timezone locationw + * @param {String} aData.tznames An alternative string representation of the + * timezone + * @param {Number} aData.latitude The latitude of the timezone + * @param {Number} aData.longitude The longitude of the timezone + */ + static fromData(aData) { + let tt = new Timezone(); + return tt.fromData(aData); + } + + /** + * The instance describing the UTC timezone + * @type {Timezone} + * @constant + * @instance + */ + static #utcTimezone = null; + static get utcTimezone() { + if (!this.#utcTimezone) { + this.#utcTimezone = Timezone.fromData({ + tzid: "UTC" + }); + } + return this.#utcTimezone; + } + + /** + * The instance describing the local timezone + * @type {Timezone} + * @constant + * @instance + */ + static #localTimezone = null; + static get localTimezone() { + if (!this.#localTimezone) { + this.#localTimezone = Timezone.fromData({ + tzid: "floating" + }); + } + return this.#localTimezone; + } + + /** + * Adjust a timezone change object. + * @private + * @param {Object} change The timezone change object + * @param {Number} days The extra amount of days + * @param {Number} hours The extra amount of hours + * @param {Number} minutes The extra amount of minutes + * @param {Number} seconds The extra amount of seconds + */ + static adjust_change(change, days, hours, minutes, seconds) { + return Time.prototype.adjust.call( + change, + days, + hours, + minutes, + seconds, + change + ); + } + + static _minimumExpansionYear = -1; + static EXTRA_COVERAGE = 5; + + /** + * Creates a new ICAL.Timezone instance, by passing in a tzid and component. + * + * @param {Component|Object} data options for class + * @param {String|Component} data.component + * If data is a simple object, then this member can be set to either a + * string containing the component data, or an already parsed + * ICAL.Component + * @param {String} data.tzid The timezone identifier + * @param {String} data.location The timezone locationw + * @param {String} data.tznames An alternative string representation of the + * timezone + * @param {Number} data.latitude The latitude of the timezone + * @param {Number} data.longitude The longitude of the timezone + */ + constructor(data) { + this.wrappedJSObject = this; + this.fromData(data); + } + + + /** + * Timezone identifier + * @type {String} + */ + tzid = ""; + + /** + * Timezone location + * @type {String} + */ + location = ""; + + /** + * Alternative timezone name, for the string representation + * @type {String} + */ + tznames = ""; + + /** + * The primary latitude for the timezone. + * @type {Number} + */ + latitude = 0.0; + + /** + * The primary longitude for the timezone. + * @type {Number} + */ + longitude = 0.0; + + /** + * The vtimezone component for this timezone. + * @type {Component} + */ + component = null; + + /** + * The year this timezone has been expanded to. All timezone transition + * dates until this year are known and can be used for calculation + * + * @private + * @type {Number} + */ + expandedUntilYear = 0; + + /** + * The class identifier. + * @constant + * @type {String} + * @default "icaltimezone" + */ + icalclass = "icaltimezone"; + + /** + * Sets up the current instance using members from the passed data object. + * + * @param {Component|Object} aData options for class + * @param {String|Component} aData.component + * If aData is a simple object, then this member can be set to either a + * string containing the component data, or an already parsed + * ICAL.Component + * @param {String} aData.tzid The timezone identifier + * @param {String} aData.location The timezone locationw + * @param {String} aData.tznames An alternative string representation of the + * timezone + * @param {Number} aData.latitude The latitude of the timezone + * @param {Number} aData.longitude The longitude of the timezone + */ + fromData(aData) { + this.expandedUntilYear = 0; + this.changes = []; + + if (aData instanceof Component) { + // Either a component is passed directly + this.component = aData; + } else { + // Otherwise the component may be in the data object + if (aData && "component" in aData) { + if (typeof aData.component == "string") { + // If a string was passed, parse it as a component + let jCal = ICALParse(aData.component); + this.component = new Component(jCal); + } else if (aData.component instanceof Component) { + // If it was a component already, then just set it + this.component = aData.component; + } else { + // Otherwise just null out the component + this.component = null; + } + } + + // Copy remaining passed properties + for (let prop of OPTIONS) { + if (aData && prop in aData) { + this[prop] = aData[prop]; + } + } + } + + // If we have a component but no TZID, attempt to get it from the + // component's properties. + if (this.component instanceof Component && !this.tzid) { + this.tzid = this.component.getFirstPropertyValue('tzid'); + } + + return this; + } + + /** + * Finds the utcOffset the given time would occur in this timezone. + * + * @param {Time} tt The time to check for + * @return {Number} utc offset in seconds + */ + utcOffset(tt) { + if (this == Timezone.utcTimezone || this == Timezone.localTimezone) { + return 0; + } + + this._ensureCoverage(tt.year); + + if (!this.changes.length) { + return 0; + } + + let tt_change = { + year: tt.year, + month: tt.month, + day: tt.day, + hour: tt.hour, + minute: tt.minute, + second: tt.second + }; + + let change_num = this._findNearbyChange(tt_change); + let change_num_to_use = -1; + let step = 1; + + // TODO: replace with bin search? + for (;;) { + let change = clone(this.changes[change_num], true); + if (change.utcOffset < change.prevUtcOffset) { + Timezone.adjust_change(change, 0, 0, 0, change.utcOffset); + } else { + Timezone.adjust_change(change, 0, 0, 0, + change.prevUtcOffset); + } + + let cmp = Timezone._compare_change_fn(tt_change, change); + + if (cmp >= 0) { + change_num_to_use = change_num; + } else { + step = -1; + } + + if (step == -1 && change_num_to_use != -1) { + break; + } + + change_num += step; + + if (change_num < 0) { + return 0; + } + + if (change_num >= this.changes.length) { + break; + } + } + + let zone_change = this.changes[change_num_to_use]; + let utcOffset_change = zone_change.utcOffset - zone_change.prevUtcOffset; + + if (utcOffset_change < 0 && change_num_to_use > 0) { + let tmp_change = clone(zone_change, true); + Timezone.adjust_change(tmp_change, 0, 0, 0, tmp_change.prevUtcOffset); + + if (Timezone._compare_change_fn(tt_change, tmp_change) < 0) { + let prev_zone_change = this.changes[change_num_to_use - 1]; + + let want_daylight = false; // TODO + + if (zone_change.is_daylight != want_daylight && + prev_zone_change.is_daylight == want_daylight) { + zone_change = prev_zone_change; + } + } + } + + // TODO return is_daylight? + return zone_change.utcOffset; + } + + _findNearbyChange(change) { + // find the closest match + let idx = binsearchInsert( + this.changes, + change, + Timezone._compare_change_fn + ); + + if (idx >= this.changes.length) { + return this.changes.length - 1; + } + + return idx; + } + + _ensureCoverage(aYear) { + if (Timezone._minimumExpansionYear == -1) { + let today = Time.now(); + Timezone._minimumExpansionYear = today.year; + } + + let changesEndYear = aYear; + if (changesEndYear < Timezone._minimumExpansionYear) { + changesEndYear = Timezone._minimumExpansionYear; + } + + changesEndYear += Timezone.EXTRA_COVERAGE; + + if (!this.changes.length || this.expandedUntilYear < aYear) { + let subcomps = this.component.getAllSubcomponents(); + let compLen = subcomps.length; + let compIdx = 0; + + for (; compIdx < compLen; compIdx++) { + this._expandComponent( + subcomps[compIdx], changesEndYear, this.changes + ); + } + + this.changes.sort(Timezone._compare_change_fn); + this.expandedUntilYear = changesEndYear; + } + } + + _expandComponent(aComponent, aYear, changes) { + if (!aComponent.hasProperty("dtstart") || + !aComponent.hasProperty("tzoffsetto") || + !aComponent.hasProperty("tzoffsetfrom")) { + return null; + } + + let dtstart = aComponent.getFirstProperty("dtstart").getFirstValue(); + let change; + + function convert_tzoffset(offset) { + return offset.factor * (offset.hours * 3600 + offset.minutes * 60); + } + + function init_changes() { + let changebase = {}; + changebase.is_daylight = (aComponent.name == "daylight"); + changebase.utcOffset = convert_tzoffset( + aComponent.getFirstProperty("tzoffsetto").getFirstValue() + ); + + changebase.prevUtcOffset = convert_tzoffset( + aComponent.getFirstProperty("tzoffsetfrom").getFirstValue() + ); + + return changebase; + } + + if (!aComponent.hasProperty("rrule") && !aComponent.hasProperty("rdate")) { + change = init_changes(); + change.year = dtstart.year; + change.month = dtstart.month; + change.day = dtstart.day; + change.hour = dtstart.hour; + change.minute = dtstart.minute; + change.second = dtstart.second; + + Timezone.adjust_change(change, 0, 0, 0, -change.prevUtcOffset); + changes.push(change); + } else { + let props = aComponent.getAllProperties("rdate"); + for (let rdate of props) { + let time = rdate.getFirstValue(); + change = init_changes(); + + change.year = time.year; + change.month = time.month; + change.day = time.day; + + if (time.isDate) { + change.hour = dtstart.hour; + change.minute = dtstart.minute; + change.second = dtstart.second; + + if (dtstart.zone != Timezone.utcTimezone) { + Timezone.adjust_change(change, 0, 0, 0, -change.prevUtcOffset); + } + } else { + change.hour = time.hour; + change.minute = time.minute; + change.second = time.second; + + if (time.zone != Timezone.utcTimezone) { + Timezone.adjust_change(change, 0, 0, 0, -change.prevUtcOffset); + } + } + + changes.push(change); + } + + let rrule = aComponent.getFirstProperty("rrule"); + + if (rrule) { + rrule = rrule.getFirstValue(); + change = init_changes(); + + if (rrule.until && rrule.until.zone == Timezone.utcTimezone) { + rrule.until.adjust(0, 0, 0, change.prevUtcOffset); + rrule.until.zone = Timezone.localTimezone; + } + + let iterator = rrule.iterator(dtstart); + + let occ; + while ((occ = iterator.next())) { + change = init_changes(); + if (occ.year > aYear || !occ) { + break; + } + + change.year = occ.year; + change.month = occ.month; + change.day = occ.day; + change.hour = occ.hour; + change.minute = occ.minute; + change.second = occ.second; + change.isDate = occ.isDate; + + Timezone.adjust_change(change, 0, 0, 0, -change.prevUtcOffset); + changes.push(change); + } + } + } + + return changes; + } + + /** + * The string representation of this timezone. + * @return {String} + */ + toString() { + return (this.tznames ? this.tznames : this.tzid); + } +} +export default Timezone; diff --git a/lib/ical/timezone_service.js b/lib/ical/timezone_service.js new file mode 100644 index 0000000..cfd8dd5 --- /dev/null +++ b/lib/ical/timezone_service.js @@ -0,0 +1,129 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */ + +import Timezone from "./timezone.js"; +// needed for typescript type resolution +// eslint-disable-next-line no-unused-vars +import Component from "./component.js"; + +let zones = null; + +/** + * @classdesc + * Singleton class to contain timezones. Right now it is all manual registry in + * the future we may use this class to download timezone information or handle + * loading pre-expanded timezones. + * + * @exports module:ICAL.TimezoneService + * @memberof ICAL + */ +const TimezoneService = { + get count() { + if (zones === null) { + return 0; + } + + return Object.keys(zones).length; + }, + + reset: function() { + zones = Object.create(null); + let utc = Timezone.utcTimezone; + + zones.Z = utc; + zones.UTC = utc; + zones.GMT = utc; + }, + _hard_reset: function() { + zones = null; + }, + + /** + * Checks if timezone id has been registered. + * + * @param {String} tzid Timezone identifier (e.g. America/Los_Angeles) + * @return {Boolean} False, when not present + */ + has: function(tzid) { + if (zones === null) { + return false; + } + + return !!zones[tzid]; + }, + + /** + * Returns a timezone by its tzid if present. + * + * @param {String} tzid Timezone identifier (e.g. America/Los_Angeles) + * @return {Timezone | undefined} The timezone, or undefined if not found + */ + get: function(tzid) { + if (zones === null) { + this.reset(); + } + + return zones[tzid]; + }, + + /** + * Registers a timezone object or component. + * + * @param {Component|Timezone} timezone + * The initialized zone or vtimezone. + * + * @param {String=} name + * The name of the timezone. Defaults to the component's TZID if not + * passed. + */ + register: function(timezone, name) { + if (zones === null) { + this.reset(); + } + + // This avoids a breaking change by the change of argument order + // TODO remove in v3 + if (typeof timezone === "string" && name instanceof Timezone) { + [timezone, name] = [name, timezone]; + } + + if (!name) { + if (timezone instanceof Timezone) { + name = timezone.tzid; + } else { + if (timezone.name === 'vtimezone') { + timezone = new Timezone(timezone); + name = timezone.tzid; + } + } + } + + if (!name) { + throw new TypeError("Neither a timezone nor a name was passed"); + } + + if (timezone instanceof Timezone) { + zones[name] = timezone; + } else { + throw new TypeError('timezone must be ICAL.Timezone or ICAL.Component'); + } + }, + + /** + * Removes a timezone by its tzid from the list. + * + * @param {String} tzid Timezone identifier (e.g. America/Los_Angeles) + * @return {?Timezone} The removed timezone, or null if not registered + */ + remove: function(tzid) { + if (zones === null) { + return null; + } + + return (delete zones[tzid]); + } +}; + +export default TimezoneService; diff --git a/lib/ical/types.js b/lib/ical/types.js new file mode 100644 index 0000000..704b3ad --- /dev/null +++ b/lib/ical/types.js @@ -0,0 +1,93 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch, 2024 */ + +// TODO: Once https://github.com/microsoft/TypeScript/issues/22160 and +// https://github.com/microsoft/TypeScript/issues/46011 is fixed, update +// @typedef(import(...)) to @import to avoid re-exporting the typedefs + +/* eslint-disable no-unused-vars */ +// needed for typescript type resolution +import Component from "./component"; +import Event from "./event"; +import Time from "./time"; + +/** + * The weekday, 1 = SUNDAY, 7 = SATURDAY. Access via + * ICAL.Time.MONDAY, ICAL.Time.TUESDAY, ... + * + * @typedef {Number} weekDay + * @memberof ICAL.Time + */ + +/** + * Possible frequency values for the FREQ part + * (YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY) + * + * @typedef {String} frequencyValues + * @memberof ICAL.Recur + */ + +/** + * This object is returned by {@link ICAL.Event#getOccurrenceDetails getOccurrenceDetails} + * @memberof ICAL.Event + * @typedef {Object} occurrenceDetails + * @property {Time} recurrenceId The passed in recurrence id + * @property {Event} item The occurrence + * @property {Time} startDate The start of the occurrence + * @property {Time} endDate The end of the occurrence + */ + +/** + * The state for parsing content lines from an iCalendar/vCard string. + * + * @private + * @memberof ICAL.parse + * @typedef {Object} parserState + * @property {designSet} designSet The design set to use for parsing + * @property {Component[]} stack The stack of components being processed + * @property {Component} component The currently active component + */ + +/** + * A jCal component. + * + * TODO: Properly typedef this when https://github.com/hegemonic/catharsis/pull/70 + * is merged. Documentation is ignored until this can be documented properly. + * + * @example + * ["vevent", [...properties here...], [...components here...] ] + * + * @ignore + * @typedef {Array} jCalComponent + * @property {String} 0 The component name + * @property {jCalProperty[]} 1 The properties of this component + * @property {jCalComponent[]} 2 The subcomponents of this component + */ + +/** + * A designSet describes value, parameter and property data. It is used by + * ther parser and stringifier in components and properties to determine they + * should be represented. + * + * @memberof ICAL.design + * @typedef {Object} designSet + * @property {Object} value Definitions for value types, keys are type names + * @property {Object} param Definitions for params, keys are param names + * @property {Object} property Definitions for properties, keys are property names + * @property {boolean} propertyGroups If content lines may include a group name + */ + +/** + * The jCal Geo type. This is a tuple representing a geographical location. + * The first element is the Latitude and the second element is the Longitude. + * + * TODO: Properly typedef this when https://github.com/hegemonic/catharsis/pull/70 + * + * @typedef {Array} Geo + * @property {Number} 0 Latitude + * @property {Number} 1 Longitude + */ + +export const _ = {}; diff --git a/lib/ical/utc_offset.js b/lib/ical/utc_offset.js new file mode 100644 index 0000000..75806f5 --- /dev/null +++ b/lib/ical/utc_offset.js @@ -0,0 +1,187 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */ + +import { strictParseInt, trunc, pad2 } from "./helpers.js"; +// needed for typescript type resolution +// eslint-disable-next-line no-unused-vars +import Duration from "./duration.js"; +import design from "./design.js"; + +/** + * This class represents the "utc-offset" value type, with various calculation and manipulation + * methods. + * + * @memberof ICAL + */ +class UtcOffset { + /** + * Creates a new {@link ICAL.UtcOffset} instance from the passed string. + * + * @param {String} aString The string to parse + * @return {Duration} The created utc-offset instance + */ + static fromString(aString) { + // -05:00 + let options = {}; + //TODO: support seconds per rfc5545 ? + options.factor = (aString[0] === '+') ? 1 : -1; + options.hours = strictParseInt(aString.slice(1, 3)); + options.minutes = strictParseInt(aString.slice(4, 6)); + + return new UtcOffset(options); + } + + /** + * Creates a new {@link ICAL.UtcOffset} instance from the passed seconds + * value. + * + * @param {Number} aSeconds The number of seconds to convert + */ + static fromSeconds(aSeconds) { + let instance = new UtcOffset(); + instance.fromSeconds(aSeconds); + return instance; + } + + /** + * Creates a new ICAL.UtcOffset instance. + * + * @param {Object} aData An object with members of the utc offset + * @param {Number=} aData.hours The hours for the utc offset + * @param {Number=} aData.minutes The minutes in the utc offset + * @param {Number=} aData.factor The factor for the utc-offset, either -1 or 1 + */ + constructor(aData) { + this.fromData(aData); + } + + /** + * The hours in the utc-offset + * @type {Number} + */ + hours = 0; + + /** + * The minutes in the utc-offset + * @type {Number} + */ + minutes = 0; + + /** + * The sign of the utc offset, 1 for positive offset, -1 for negative + * offsets. + * @type {Number} + */ + factor = 1; + + /** + * The type name, to be used in the jCal object. + * @constant + * @type {String} + * @default "utc-offset" + */ + icaltype = "utc-offset"; + + /** + * Returns a clone of the utc offset object. + * + * @return {UtcOffset} The cloned object + */ + clone() { + return UtcOffset.fromSeconds(this.toSeconds()); + } + + /** + * Sets up the current instance using members from the passed data object. + * + * @param {Object} aData An object with members of the utc offset + * @param {Number=} aData.hours The hours for the utc offset + * @param {Number=} aData.minutes The minutes in the utc offset + * @param {Number=} aData.factor The factor for the utc-offset, either -1 or 1 + */ + fromData(aData) { + if (aData) { + for (let [key, value] of Object.entries(aData)) { + this[key] = value; + } + } + this._normalize(); + } + + /** + * Sets up the current instance from the given seconds value. The seconds + * value is truncated to the minute. Offsets are wrapped when the world + * ends, the hour after UTC+14:00 is UTC-12:00. + * + * @param {Number} aSeconds The seconds to convert into an offset + */ + fromSeconds(aSeconds) { + let secs = Math.abs(aSeconds); + + this.factor = aSeconds < 0 ? -1 : 1; + this.hours = trunc(secs / 3600); + + secs -= (this.hours * 3600); + this.minutes = trunc(secs / 60); + return this; + } + + /** + * Convert the current offset to a value in seconds + * + * @return {Number} The offset in seconds + */ + toSeconds() { + return this.factor * (60 * this.minutes + 3600 * this.hours); + } + + /** + * Compare this utc offset with another one. + * + * @param {UtcOffset} other The other offset to compare with + * @return {Number} -1, 0 or 1 for less/equal/greater + */ + compare(other) { + let a = this.toSeconds(); + let b = other.toSeconds(); + return (a > b) - (b > a); + } + + _normalize() { + // Range: 97200 seconds (with 1 hour inbetween) + let secs = this.toSeconds(); + let factor = this.factor; + while (secs < -43200) { // = UTC-12:00 + secs += 97200; + } + while (secs > 50400) { // = UTC+14:00 + secs -= 97200; + } + + this.fromSeconds(secs); + + // Avoid changing the factor when on zero seconds + if (secs == 0) { + this.factor = factor; + } + } + + /** + * The iCalendar string representation of this utc-offset. + * @return {String} + */ + toICALString() { + return design.icalendar.value['utc-offset'].toICAL(this.toString()); + } + + /** + * The string representation of this utc-offset. + * @return {String} + */ + toString() { + return (this.factor == 1 ? "+" : "-") + pad2(this.hours) + ':' + pad2(this.minutes); + } +} +export default UtcOffset; diff --git a/lib/ical/vcard_time.js b/lib/ical/vcard_time.js new file mode 100644 index 0000000..85765b7 --- /dev/null +++ b/lib/ical/vcard_time.js @@ -0,0 +1,183 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */ + +import UtcOffset from "./utc_offset.js"; +import Time from "./time.js"; +import Timezone from "./timezone.js"; +import design from "./design.js"; +import { pad2, strictParseInt } from "./helpers.js"; + +/** + * Describes a vCard time, which has slight differences to the ICAL.Time. + * Properties can be null if not specified, for example for dates with + * reduced accuracy or truncation. + * + * Note that currently not all methods are correctly re-implemented for + * VCardTime. For example, comparison will have undefined results when some + * members are null. + * + * Also, normalization is not yet implemented for this class! + * + * @memberof ICAL + * @extends {ICAL.Time} + */ +class VCardTime extends Time { + /** + * Returns a new ICAL.VCardTime instance from a date and/or time string. + * + * @param {String} aValue The string to create from + * @param {String} aIcalType The type for this instance, e.g. date-and-or-time + * @return {VCardTime} The date/time instance + */ + static fromDateAndOrTimeString(aValue, aIcalType) { + function part(v, s, e) { + return v ? strictParseInt(v.slice(s, s + e)) : null; + } + let parts = aValue.split('T'); + let dt = parts[0], tmz = parts[1]; + let splitzone = tmz ? design.vcard.value.time._splitZone(tmz) : []; + let zone = splitzone[0], tm = splitzone[1]; + + let dtlen = dt ? dt.length : 0; + let tmlen = tm ? tm.length : 0; + + let hasDashDate = dt && dt[0] == '-' && dt[1] == '-'; + let hasDashTime = tm && tm[0] == '-'; + + let o = { + year: hasDashDate ? null : part(dt, 0, 4), + month: hasDashDate && (dtlen == 4 || dtlen == 7) ? part(dt, 2, 2) : dtlen == 7 ? part(dt, 5, 2) : dtlen == 10 ? part(dt, 5, 2) : null, + day: dtlen == 5 ? part(dt, 3, 2) : dtlen == 7 && hasDashDate ? part(dt, 5, 2) : dtlen == 10 ? part(dt, 8, 2) : null, + + hour: hasDashTime ? null : part(tm, 0, 2), + minute: hasDashTime && tmlen == 3 ? part(tm, 1, 2) : tmlen > 4 ? hasDashTime ? part(tm, 1, 2) : part(tm, 3, 2) : null, + second: tmlen == 4 ? part(tm, 2, 2) : tmlen == 6 ? part(tm, 4, 2) : tmlen == 8 ? part(tm, 6, 2) : null + }; + + if (zone == 'Z') { + zone = Timezone.utcTimezone; + } else if (zone && zone[3] == ':') { + zone = UtcOffset.fromString(zone); + } else { + zone = null; + } + + return new VCardTime(o, zone, aIcalType); + } + + + /** + * Creates a new ICAL.VCardTime instance. + * + * @param {Object} data The data for the time instance + * @param {Number=} data.year The year for this date + * @param {Number=} data.month The month for this date + * @param {Number=} data.day The day for this date + * @param {Number=} data.hour The hour for this date + * @param {Number=} data.minute The minute for this date + * @param {Number=} data.second The second for this date + * @param {Timezone|UtcOffset} zone The timezone to use + * @param {String} icaltype The type for this date/time object + */ + constructor(data, zone, icaltype) { + super(data, zone); + this.icaltype = icaltype || "date-and-or-time"; + } + + /** + * The class identifier. + * @constant + * @type {String} + * @default "vcardtime" + */ + icalclass = "vcardtime"; + + /** + * The type name, to be used in the jCal object. + * @type {String} + * @default "date-and-or-time" + */ + icaltype = "date-and-or-time"; + + /** + * Returns a clone of the vcard date/time object. + * + * @return {VCardTime} The cloned object + */ + clone() { + return new VCardTime(this._time, this.zone, this.icaltype); + } + + _normalize() { + return this; + } + + /** + * @inheritdoc + */ + utcOffset() { + if (this.zone instanceof UtcOffset) { + return this.zone.toSeconds(); + } else { + return Time.prototype.utcOffset.apply(this, arguments); + } + } + + /** + * Returns an RFC 6350 compliant representation of this object. + * + * @return {String} vcard date/time string + */ + toICALString() { + return design.vcard.value[this.icaltype].toICAL(this.toString()); + } + + /** + * The string representation of this date/time, in jCard form + * (including : and - separators). + * @return {String} + */ + toString() { + let y = this.year, m = this.month, d = this.day; + let h = this.hour, mm = this.minute, s = this.second; + + let hasYear = y !== null, hasMonth = m !== null, hasDay = d !== null; + let hasHour = h !== null, hasMinute = mm !== null, hasSecond = s !== null; + + let datepart = (hasYear ? pad2(y) + (hasMonth || hasDay ? '-' : '') : (hasMonth || hasDay ? '--' : '')) + + (hasMonth ? pad2(m) : '') + + (hasDay ? '-' + pad2(d) : ''); + let timepart = (hasHour ? pad2(h) : '-') + (hasHour && hasMinute ? ':' : '') + + (hasMinute ? pad2(mm) : '') + (!hasHour && !hasMinute ? '-' : '') + + (hasMinute && hasSecond ? ':' : '') + + (hasSecond ? pad2(s) : ''); + + let zone; + if (this.zone === Timezone.utcTimezone) { + zone = 'Z'; + } else if (this.zone instanceof UtcOffset) { + zone = this.zone.toString(); + } else if (this.zone === Timezone.localTimezone) { + zone = ''; + } else if (this.zone instanceof Timezone) { + let offset = UtcOffset.fromSeconds(this.zone.utcOffset(this)); + zone = offset.toString(); + } else { + zone = ''; + } + + switch (this.icaltype) { + case "time": + return timepart + zone; + case "date-and-or-time": + case "date-time": + return datepart + (timepart == '--' ? '' : 'T' + timepart + zone); + case "date": + return datepart; + } + return null; + } +} +export default VCardTime; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..5dae3d6 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,11026 @@ +{ + "name": "ical.js", + "version": "2.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ical.js", + "version": "2.0.1", + "license": "MPL-2.0", + "devDependencies": { + "@babel/preset-env": "^7.24.3", + "@eslint/js": "^9.0.0", + "@octokit/core": "^6.0.1", + "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-terser": "^0.4.4", + "@rollup/plugin-typescript": "^11.1.6", + "@stylistic/eslint-plugin": "^2.1.0", + "benchmark": "^2.1.4", + "c8": "^10.1.2", + "chai": "^5.1.0", + "clean-jsdoc-theme": "^4.2.18", + "eslint": "^9.0.0", + "eslint-plugin-html": "^8.1.1", + "globals": "^15.0.0", + "jsdoc": "^4.0.2", + "jsdoc-tsimport-plugin": "^1.0.5", + "karma": "^6.4.3", + "karma-chai": "^0.1.0", + "karma-mocha": "^2.0.1", + "karma-sauce-launcher": "^4.3.6", + "karma-spec-reporter": "^0.0.36", + "mocha": "^10.3.0", + "node-fetch": "^3.3.2", + "yauzl-promise": "^4.0.0" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", + "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.3.tgz", + "integrity": "sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ==", + "dev": true, + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.1", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.24.1", + "@babel/parser": "^7.24.1", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.25.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.5.tgz", + "integrity": "sha512-abd43wyLfbWoxC6ahM8xTkqLpGB2iWBVyuKC9/srhFunCd1SDNrV1s72bBpK4hLj8KLzHBBcOblvLQZBNw9r3w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.4", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", + "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.4.tgz", + "integrity": "sha512-ro/bFs3/84MDgDmMwbcHgDa8/E6J3QKNTk4xJJnVeFtGE+tL0K26E3pNxhYz2b67fJpt7Aphw5XcploKXuCvCQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/helper-replace-supers": "^7.25.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/traverse": "^7.25.4", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.2.tgz", + "integrity": "sha512-+wqVGP+DFmqwFD3EH6TMTfUNeqDehV3E/dl+Sd54eaXqm17tEUNbEIn4sVivVowbvUpOtIGxdo3GoXyDH9N/9g==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", + "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", + "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.8", + "@babel/types": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", + "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.0.tgz", + "integrity": "sha512-NhavI2eWEIz/H9dbrG0TuOicDhNexze43i5z7lEqwYm0WEZVTwnPpA0EafUTP7+6/W79HWIP2cTe3Z5NiSTVpw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-wrap-function": "^7.25.0", + "@babel/traverse": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz", + "integrity": "sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==", + "dev": true, + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/traverse": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", + "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.0.tgz", + "integrity": "sha512-s6Q1ebqutSiZnEjaofc/UKDyC4SbzV5n5SrA2Gq8UawLycr3i04f1dX4OzoQVnexm6aOCh37SQNYlJ/8Ku+PMQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.0", + "@babel/types": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.1.tgz", + "integrity": "sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.4.tgz", + "integrity": "sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.3.tgz", + "integrity": "sha512-wUrcsxZg6rqBXG05HG1FPYgsP6EvwF4WpBbxIpWIIYnH8wG0gzx3yZY3dtEHas4sTAOGkbTsc9EGPxwff8lRoA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.0.tgz", + "integrity": "sha512-Bm4bH2qsX880b/3ziJ8KD711LT7z4u8CFudmjqle65AZj/HNUFhEf90dqYv6O86buWvSBmeQDjv0Tn2aF/bIBA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.0.tgz", + "integrity": "sha512-lXwdNZtTmeVOOFtwM/WDe7yg1PL8sYhRk/XH0FzbR2HDQ0xC+EnQ/JHeoMYSavtU115tnUk0q9CDyq8si+LMAA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz", + "integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.0.tgz", + "integrity": "sha512-tggFrk1AIShG/RUQbEwt2Tr/E+ObkfwrPjR6BjbRvsx24+PSjK8zrq0GWPNCjo8qpRx4DuJzlcvWJqlm+0h3kw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz", + "integrity": "sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", + "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.4.tgz", + "integrity": "sha512-jz8cV2XDDTqjKPwVPJBIjORVEmSGYhdRa8e5k5+vN+uwcjSrSxUaebBRa4ko1jqNF2uxyg8G6XYk30Jv285xzg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-remap-async-to-generator": "^7.25.0", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/traverse": "^7.25.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", + "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", + "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.0.tgz", + "integrity": "sha512-yBQjYoOjXlFv9nlXb3f1casSHOZkWr29NX+zChVanLg5Nc157CrbEX9D7hxxtTpuFy7Q0YzmmWfJxzvps4kXrQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.4.tgz", + "integrity": "sha512-nZeZHyCWPfjkdU5pA/uHiTaDAFUEqkpzf1YoQT2NeSynCGYq9rxfyI3XpQbfx/a0hSnFH6TGlEXvae5Vi7GD8g==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.4", + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz", + "integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.4.tgz", + "integrity": "sha512-oexUfaQle2pF/b6E0dwsxQtAol9TLSO88kQvym6HHBWFliV2lGdrPieX+WgMRLSJDVzdYywk7jXbLPuO2KLTLg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-replace-supers": "^7.25.0", + "@babel/traverse": "^7.25.4", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", + "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/template": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz", + "integrity": "sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", + "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", + "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.0.tgz", + "integrity": "sha512-YLpb4LlYSc3sCUa35un84poXoraOiQucUTTu8X1j18JV+gNa8E0nyUf/CjZ171IRGr4jEguF+vzJU66QZhn29g==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", + "integrity": "sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", + "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz", + "integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", + "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.25.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.1.tgz", + "integrity": "sha512-TVVJVdW9RKMNgJJlLtHsKDTydjZAbwIsn6ySBPQaEAUU5+gVvlJt/9nRmqVbsV/IBanRjzWoaAQKLoamWVOUuA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz", + "integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.2.tgz", + "integrity": "sha512-HQI+HcTbm9ur3Z2DkO+jgESMAMcYLuN/A7NRw9juzxAezN9AvqvUTnpKP/9kkYANz6u7dFlAyOu44ejuGySlfw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz", + "integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", + "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", + "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz", + "integrity": "sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-simple-access": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz", + "integrity": "sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", + "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", + "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", + "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz", + "integrity": "sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz", + "integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz", + "integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", + "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz", + "integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz", + "integrity": "sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", + "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.4.tgz", + "integrity": "sha512-ao8BG7E2b/URaUQGqN3Tlsg+M3KlHY6rJ1O1gXAEUnZoyNQnvKyH87Kfg+FoxSeyWUB8ISZZsC91C44ZuBFytw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.4", + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz", + "integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", + "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", + "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", + "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", + "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", + "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", + "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", + "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz", + "integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", + "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz", + "integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", + "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.4.tgz", + "integrity": "sha512-qesBxiWkgN1Q+31xUE9RcMk79eOXXDCv6tfyGMRSs4RGlioSg2WVyQAm07k726cSE56pa+Kb0y9epX2qaXzTvA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.2", + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.4.tgz", + "integrity": "sha512-W9Gyo+KmcxjGahtt3t9fb14vFRWvPpu5pT6GBlovAK6BTBcxgjfVMSQCfJl4oi35ODrxP6xx2Wr8LNST57Mraw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.25.4", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-option": "^7.24.8", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.3", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.0", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.0", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.0", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.24.7", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.25.4", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoped-functions": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.25.4", + "@babel/plugin-transform-class-static-block": "^7.24.7", + "@babel/plugin-transform-classes": "^7.25.4", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-dotall-regex": "^7.24.7", + "@babel/plugin-transform-duplicate-keys": "^7.24.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.0", + "@babel/plugin-transform-dynamic-import": "^7.24.7", + "@babel/plugin-transform-exponentiation-operator": "^7.24.7", + "@babel/plugin-transform-export-namespace-from": "^7.24.7", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.25.1", + "@babel/plugin-transform-json-strings": "^7.24.7", + "@babel/plugin-transform-literals": "^7.25.2", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-member-expression-literals": "^7.24.7", + "@babel/plugin-transform-modules-amd": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-modules-systemjs": "^7.25.0", + "@babel/plugin-transform-modules-umd": "^7.24.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-new-target": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-object-super": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.25.4", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-property-literals": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-reserved-words": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-template-literals": "^7.24.7", + "@babel/plugin-transform-typeof-symbol": "^7.24.8", + "@babel/plugin-transform-unicode-escapes": "^7.24.7", + "@babel/plugin-transform-unicode-property-regex": "^7.24.7", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.25.4", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.6", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.37.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "node_modules/@babel/runtime": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz", + "integrity": "sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.4.tgz", + "integrity": "sha512-VJ4XsrD+nOvlXyLzmLzUs/0qjFS4sK30te5yEFlvbbUNEgKaVb2BHZUpAL+ttLPQAHNrsI3zZisbfha5Cvr8vg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.4", + "@babel/parser": "^7.25.4", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.4", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.4.tgz", + "integrity": "sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", + "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.10.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz", + "integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz", + "integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==", + "dev": true, + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", + "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsdoc/salty": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.7.tgz", + "integrity": "sha512-mh8LbS9d4Jq84KLw8pzho7XC2q2/IJGiJss3xwRoLD1A+EE16SjN4PfaG4jRCzKegTFLlN0Zd8SdUPE6XdoPFg==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v12.0.0" + } + }, + "node_modules/@node-rs/crc32": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@node-rs/crc32/-/crc32-1.10.0.tgz", + "integrity": "sha512-SFvU8PGZexRMRPUhi+4a9LW4oqFuK5CLEElysrKoRtNkJ+LcRFL+b3wfuzbcDq/ea0rS0nzRLFZwVsNVyWaGew==", + "dev": true, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@node-rs/crc32-android-arm-eabi": "1.10.0", + "@node-rs/crc32-android-arm64": "1.10.0", + "@node-rs/crc32-darwin-arm64": "1.10.0", + "@node-rs/crc32-darwin-x64": "1.10.0", + "@node-rs/crc32-freebsd-x64": "1.10.0", + "@node-rs/crc32-linux-arm-gnueabihf": "1.10.0", + "@node-rs/crc32-linux-arm64-gnu": "1.10.0", + "@node-rs/crc32-linux-arm64-musl": "1.10.0", + "@node-rs/crc32-linux-x64-gnu": "1.10.0", + "@node-rs/crc32-linux-x64-musl": "1.10.0", + "@node-rs/crc32-wasm32-wasi": "1.10.0", + "@node-rs/crc32-win32-arm64-msvc": "1.10.0", + "@node-rs/crc32-win32-ia32-msvc": "1.10.0", + "@node-rs/crc32-win32-x64-msvc": "1.10.0" + } + }, + "node_modules/@node-rs/crc32-darwin-arm64": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-darwin-arm64/-/crc32-darwin-arm64-1.10.0.tgz", + "integrity": "sha512-nAAdxZqxFBxqhI3ZMEGi7QDwg44N4laYO4iGIGhjLvsUDqJlYeIlqZ39Lh2gOK3D2uF/TaT4b0bU5EPHWnKMOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-darwin-x64": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-darwin-x64/-/crc32-darwin-x64-1.10.0.tgz", + "integrity": "sha512-0YhLJFDY7VAKlJ4+SdfZFY+u0X18tkuD3NCtPp1SYh1o9pWpFVBbTKWvdjjx/Ihqw0ozkfc3iewFJU7vlrDQJg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@octokit/auth-token": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.0.1.tgz", + "integrity": "sha512-RTmWsLfig8SBoiSdgvCht4BXl1CHU89Co5xiQ5JF19my/sIRDFCQ1RPrmK0exgqUZuNm39C/bV8+/83+MJEjGg==", + "dev": true, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.2.tgz", + "integrity": "sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==", + "dev": true, + "dependencies": { + "@octokit/auth-token": "^5.0.0", + "@octokit/graphql": "^8.0.0", + "@octokit/request": "^9.0.0", + "@octokit/request-error": "^6.0.1", + "@octokit/types": "^13.0.0", + "before-after-hook": "^3.0.2", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core/node_modules/@octokit/openapi-types": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-21.2.0.tgz", + "integrity": "sha512-xx+Xd6I7rYvul/hgUDqv6TeGX0IOGnhSg9IOeYgd/uI7IAqUy6DE2B6Ipv2M4mWoxaMcWjIzgTIcv8pMO3F3vw==", + "dev": true + }, + "node_modules/@octokit/core/node_modules/@octokit/types": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.0.0.tgz", + "integrity": "sha512-jSOgEoFZvjg78txlb7cuRTAEvyyQkIEB4Nujg5ZN7E1xaICsr8A0X045Nwb1wUWNrBUHBHZNtcsDIhk8d8ipCw==", + "dev": true, + "dependencies": { + "@octokit/openapi-types": "^21.0.0" + } + }, + "node_modules/@octokit/endpoint": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.0.0.tgz", + "integrity": "sha512-emBcNDxBdC1y3+knJonS5zhUB/CG6TihubxM2U1/pG/Z1y3a4oV0Gzz3lmkCvWWQI6h3tqBAX9MgCBFp+M68Jw==", + "dev": true, + "dependencies": { + "@octokit/types": "^12.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.0.1.tgz", + "integrity": "sha512-lLDb6LhC1gBj2CxEDa5Xk10+H/boonhs+3Mi6jpRyetskDKNHe6crMeKmUE2efoLofMP8ruannLlCUgpTFmVzQ==", + "dev": true, + "dependencies": { + "@octokit/request": "^9.0.0", + "@octokit/types": "^12.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", + "dev": true + }, + "node_modules/@octokit/request": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.0.1.tgz", + "integrity": "sha512-kL+cAcbSl3dctYLuJmLfx6Iku2MXXy0jszhaEIjQNaCp4zjHXrhVAHeuaRdNvJjW9qjl3u1MJ72+OuBP0YW/pg==", + "dev": true, + "dependencies": { + "@octokit/endpoint": "^10.0.0", + "@octokit/request-error": "^6.0.1", + "@octokit/types": "^12.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.0.2.tgz", + "integrity": "sha512-WtRVpoHcNXs84+s9s/wqfHaxM68NGMg8Av7h59B50OVO0PwwMx+2GgQ/OliUd0iQBSNWgR6N8afi/KjSHbXHWw==", + "dev": true, + "dependencies": { + "@octokit/types": "^12.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "dev": true, + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.2.3.tgz", + "integrity": "sha512-bJ0UBsk0ESOs6RFcLXOt99a3yTDcOKlzfjad+rhFwdaG1Lu/Wzq58GHYCDTlZ9z6mldf4g+NTb+TXEfe0PpnsQ==", + "dev": true, + "dependencies": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.4.0", + "semver": "7.6.0", + "tar-fs": "3.0.5", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@puppeteer/browsers/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@puppeteer/browsers/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@puppeteer/browsers/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@puppeteer/browsers/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@puppeteer/browsers/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@puppeteer/browsers/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/plugin-babel": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.0.4.tgz", + "integrity": "sha512-YF7Y52kFdFT/xVSuVdjkV5ZdX/3YtmX0QulG+x0taQOtJdHYzVU61aSSkAgVJ7NOv6qPkIYiJSgSWWN/DM5sGw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@rollup/pluginutils": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + }, + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-terser": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", + "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", + "dev": true, + "dependencies": { + "serialize-javascript": "^6.0.1", + "smob": "^1.0.0", + "terser": "^5.17.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-typescript": { + "version": "11.1.6", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-11.1.6.tgz", + "integrity": "sha512-R92yOmIACgYdJ7dJ97p4K69I8gg6IEHt8M7dUBxN3W6nrO8uUxX5ixl0yU/N3aZTi8WhPuICvOHXQvF6FaykAA==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.1.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.14.0||^3.0.0||^4.0.0", + "tslib": "*", + "typescript": ">=3.7.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + }, + "tslib": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", + "dev": true + }, + "node_modules/@stylistic/eslint-plugin": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.8.0.tgz", + "integrity": "sha512-Ufvk7hP+bf+pD35R/QfunF793XlSRIC7USr3/EdgduK9j13i2JjmsM0LUz3/foS+jDYp2fzyWZA9N44CPur0Ow==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^8.4.0", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.1.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "dev": true + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true + }, + "node_modules/@types/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-4NpsnpYl2Gt1ljyBGrKMxFYAYvpqbnnkgP/i/g+NLpjEUa3obn1XJCur9YbEXKDAkaXqsR1LbDnGEJ0MmKFxfg==", + "dev": true, + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.11.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", + "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/puppeteer": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-7.0.4.tgz", + "integrity": "sha512-ja78vquZc8y+GM2al07GZqWDKQskQXygCDiu0e3uO0DMRKqE0MjrFBFmTulfPYzLB6WnL7Kl2tFPy0WXSpPomg==", + "deprecated": "This is a stub types definition. puppeteer provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "puppeteer": "*" + } + }, + "node_modules/@types/puppeteer-core": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@types/puppeteer-core/-/puppeteer-core-5.4.0.tgz", + "integrity": "sha512-yqRPuv4EFcSkTyin6Yy17pN6Qz2vwVwTCJIDYMXbE3j8vTPhv0nCQlZOl5xfi0WHUkqvQsjAR8hAfjeMCoetwg==", + "dev": true, + "dependencies": { + "@types/puppeteer": "*" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/which": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/which/-/which-1.3.2.tgz", + "integrity": "sha512-8oDqyLC7eD4HM307boe2QWKyuzdzWBj56xI/imSl2cpL+U3tCMaTAkMJ4ee5JBZ/FsOJlvRGeIShiZDAl1qERA==", + "dev": true + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.5.0.tgz", + "integrity": "sha512-06JOQ9Qgj33yvBEx6tpC8ecP9o860rsR22hWMEd12WcTRrfaFgHr2RB/CA/B+7BMhHkXT4chg2MyboGdFGawYg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.5.0", + "@typescript-eslint/visitor-keys": "8.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.5.0.tgz", + "integrity": "sha512-qjkormnQS5wF9pjSi6q60bKUHH44j2APxfh9TQRXK8wbYVeDYYdYJGIROL87LGZZ2gz3Rbmjc736qyL8deVtdw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.5.0.tgz", + "integrity": "sha512-vEG2Sf9P8BPQ+d0pxdfndw3xIXaoSjliG0/Ejk7UggByZPKXmJmw3GW5jV2gHNQNawBUyfahoSiCFVov0Ruf7Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.5.0", + "@typescript-eslint/visitor-keys": "8.5.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.5.0.tgz", + "integrity": "sha512-6yyGYVL0e+VzGYp60wvkBHiqDWOpT63pdMV2CVG4LVDd5uR6q1qQN/7LafBZtAtNIn/mqXjsSeS5ggv/P0iECw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.5.0", + "@typescript-eslint/types": "8.5.0", + "@typescript-eslint/typescript-estree": "8.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.5.0.tgz", + "integrity": "sha512-yTPqMnbAZJNy2Xq2XU8AdtOW9tJIr+UQb64aXB9f3B1498Zx9JorVgFJcZpEc9UBuCCrdzKID2RGAMkYcDtZOw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.5.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@wdio/config": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-6.12.1.tgz", + "integrity": "sha512-V5hTIW5FNlZ1W33smHF4Rd5BKjGW2KeYhyXDQfXHjqLCeRiirZ9fABCo9plaVQDnwWSUMWYaAaIAifV82/oJCQ==", + "dev": true, + "dependencies": { + "@wdio/logger": "6.10.10", + "deepmerge": "^4.0.0", + "glob": "^7.1.2" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@wdio/logger": { + "version": "6.10.10", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-6.10.10.tgz", + "integrity": "sha512-2nh0hJz9HeZE0VIEMI+oPgjr/Q37ohrR9iqsl7f7GW5ik+PnKYCT9Eab5mR1GNMG60askwbskgGC1S9ygtvrSw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@wdio/logger/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@wdio/logger/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@wdio/logger/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@wdio/logger/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@wdio/logger/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wdio/logger/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wdio/protocols": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-6.12.0.tgz", + "integrity": "sha512-UhTBZxClCsM3VjaiDp4DoSCnsa7D1QNmI2kqEBfIpyNkT3GcZhJb7L+nL0fTkzCwi7+/uLastb3/aOwH99gt0A==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@wdio/repl": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-6.11.0.tgz", + "integrity": "sha512-FxrFKiTkFyELNGGVEH1uijyvNY7lUpmff6x+FGskFGZB4uSRs0rxkOMaEjxnxw7QP1zgQKr2xC7GyO03gIGRGg==", + "dev": true, + "dependencies": { + "@wdio/utils": "6.11.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@wdio/utils": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-6.11.0.tgz", + "integrity": "sha512-vf0sOQzd28WbI26d6/ORrQ4XKWTzSlWLm9W/K/eJO0NASKPEzR+E+Q2kaa+MJ4FKXUpjbt+Lxfo+C26TzBk7tg==", + "dev": true, + "dependencies": { + "@wdio/logger": "6.10.10" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/archive-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/archive-type/-/archive-type-4.0.0.tgz", + "integrity": "sha512-zV4Ky0v1F8dBrdYElwTvQhweQ0P7Kwc1aluqJsYtOBP01jXcWCyW2IEfI1YiqsG+Iy7ZR+o5LF1N+PGECBxHWA==", + "dev": true, + "dependencies": { + "file-type": "^4.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/archive-type/node_modules/file-type": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-4.4.0.tgz", + "integrity": "sha512-f2UbFQEk7LXgWpi5ntcO86OeA/cC80fuDDDaX/fZ2ZGel+AF7leRQqBBW1eJNiiQkrZlAoM6P+VYP5P6bOlDEQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "dev": true, + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dev": true, + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver/node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/archiver/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true, + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/b4a": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", + "dev": true + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz", + "integrity": "sha512-rpIuu//y5OX6jVU+a5BCn1R5RSZYWAl2Nar76iwaOdycqb6JPxediskWFMMl7stfwNJR4b7eiQvh5fB5TEQJTQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.1", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.1.tgz", + "integrity": "sha512-JfTApdE++cgcTWjsiCQlLyFBMbTUft9ja17saCc93lgV33h4tuCVj7tlvu//qpLwaG+3yEz7/KhahGrUMkVq9g==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/bare-events": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.4.2.tgz", + "integrity": "sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==", + "dev": true, + "optional": true + }, + "node_modules/bare-fs": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.1.tgz", + "integrity": "sha512-W/Hfxc/6VehXlsgFtbB5B4xFcsCl+pAh30cYhoFyXErf6oGrwjh8SwiPAdHgpmWonKuYpZgGywN0SXt7dgsADA==", + "dev": true, + "optional": true, + "dependencies": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^2.0.0" + } + }, + "node_modules/bare-os": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.0.tgz", + "integrity": "sha512-v8DTT08AS/G0F9xrhyLtepoo9EJBJ85FRSMbu1pQUlAf6A8T0tEEQGMVObWeqpjhSPXsE0VGlluFBJu2fdoTNg==", + "dev": true, + "optional": true + }, + "node_modules/bare-path": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", + "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", + "dev": true, + "optional": true, + "dependencies": { + "bare-os": "^2.1.0" + } + }, + "node_modules/bare-stream": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.1.3.tgz", + "integrity": "sha512-tiDAH9H/kP+tvNO5sczyn9ZAA7utrSMobyDchsnyyXBuUe2FSQWbxhtuHB8jwpHYYevVo2UJpcmvvjrbHboUUQ==", + "dev": true, + "optional": true, + "dependencies": { + "streamx": "^2.18.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true, + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/before-after-hook": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", + "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", + "dev": true + }, + "node_modules/benchmark": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz", + "integrity": "sha512-l9MlfN4M1K/H2fbhfMy3B7vJd6AGKJVQn2h6Sg/Yx+KckoUA7ewS5Vv6TjSq18ooE1kS9hhAlQRH3AkXIh/aOQ==", + "dev": true, + "dependencies": { + "lodash": "^4.17.4", + "platform": "^1.3.3" + } + }, + "node_modules/bin-check": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bin-check/-/bin-check-4.1.0.tgz", + "integrity": "sha512-b6weQyEUKsDGFlACWSIOfveEnImkJyK/FGW6FAG42loyoquvjdtOIqO6yBFzHyqyVVhNgNkQxxx09SFLK28YnA==", + "dev": true, + "dependencies": { + "execa": "^0.7.0", + "executable": "^4.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-version": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bin-version/-/bin-version-3.1.0.tgz", + "integrity": "sha512-Mkfm4iE1VFt4xd4vH+gx+0/71esbfus2LsnCGe8Pi4mndSPyT+NGES/Eg99jx8/lUGWfu3z2yuB/bt5UB+iVbQ==", + "dev": true, + "dependencies": { + "execa": "^1.0.0", + "find-versions": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-version-check": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/bin-version-check/-/bin-version-check-4.0.0.tgz", + "integrity": "sha512-sR631OrhC+1f8Cvs8WyVWOA33Y8tgwjETNPyyD/myRBXLkfS/vl74FmH/lFcRl9KY3zwGh7jFhvyk9vV3/3ilQ==", + "dev": true, + "dependencies": { + "bin-version": "^3.0.0", + "semver": "^5.6.0", + "semver-truncate": "^1.1.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-version-check/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/bin-version/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/bin-version/node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-version/node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-version/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-version/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/bin-version/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bin-version/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bin-version/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/bin-wrapper": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bin-wrapper/-/bin-wrapper-4.1.0.tgz", + "integrity": "sha512-hfRmo7hWIXPkbpi0ZltboCMVrU+0ClXR/JgbCKKjlDjQf6igXa7OwdqNcFWQZPZTgiY7ZpzE3+LjjkLiTN2T7Q==", + "dev": true, + "dependencies": { + "bin-check": "^4.1.0", + "bin-version-check": "^4.0.0", + "download": "^7.1.0", + "import-lazy": "^3.1.0", + "os-filter-obj": "^2.0.0", + "pify": "^4.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "dev": true, + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/browserslist": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dev": true, + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "dev": true + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", + "dev": true + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/c8": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.2.tgz", + "integrity": "sha512-Qr6rj76eSshu5CgRYvktW0uM0CFY0yi4Fd5D0duDXO6sYinyopmftUiJVuzBQxQcwQLor7JWDVRP+dUfCmzgJw==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^7.0.1", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "monocart-coverage-reports": "^2" + }, + "peerDependenciesMeta": { + "monocart-coverage-reports": { + "optional": true + } + } + }, + "node_modules/c8/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/c8/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/c8/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001651", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", + "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/capital-case": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", + "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "dev": true, + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/caw": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz", + "integrity": "sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==", + "dev": true, + "dependencies": { + "get-proxy": "^2.0.0", + "isurl": "^1.0.0-alpha5", + "tunnel-agent": "^0.6.0", + "url-to-options": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chai": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", + "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "dev": true, + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/change-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", + "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", + "dev": true, + "dependencies": { + "camel-case": "^4.1.2", + "capital-case": "^1.0.4", + "constant-case": "^3.0.4", + "dot-case": "^3.0.4", + "header-case": "^2.0.4", + "no-case": "^3.0.4", + "param-case": "^3.0.4", + "pascal-case": "^3.1.2", + "path-case": "^3.0.4", + "sentence-case": "^3.0.4", + "snake-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "node_modules/chrome-launcher": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.13.4.tgz", + "integrity": "sha512-nnzXiDbGKjDSK6t2I+35OAPBy5Pw/39bgkb/ZAFwMhwJbdYBp6aH+vW28ZgtjdU890Q7D+3wN/tB8N66q5Gi2A==", + "dev": true, + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^1.0.5", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^0.5.3", + "rimraf": "^3.0.2" + } + }, + "node_modules/chrome-launcher/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/chromium-bidi": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.5.23.tgz", + "integrity": "sha512-1o/gLU9wDqbN5nL2MtfjykjOuighGXc3/hnWueO1haiEoFgX8h5vbvcA4tgdQfjw1mkZ1OEF4x/+HVeqEX6NoA==", + "dev": true, + "dependencies": { + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0", + "zod": "3.23.8" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "dev": true, + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/clean-jsdoc-theme": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/clean-jsdoc-theme/-/clean-jsdoc-theme-4.3.0.tgz", + "integrity": "sha512-QMrBdZ2KdPt6V2Ytg7dIt0/q32U4COpxvR0UDhPjRRKRL0o0MvRCR5YpY37/4rPF1SI1AYEKAWyof7ndCb/dzA==", + "dev": true, + "dependencies": { + "@jsdoc/salty": "^0.2.4", + "fs-extra": "^10.1.0", + "html-minifier-terser": "^7.2.0", + "klaw-sync": "^6.0.0", + "lodash": "^4.17.21", + "showdown": "^2.1.0" + }, + "peerDependencies": { + "jsdoc": ">=3.x <=4.x" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "dev": true, + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/compress-commons/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/constant-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", + "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case": "^2.0.2" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/core-js": { + "version": "3.36.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.36.1.tgz", + "integrity": "sha512-BTvUrwxVBezj5SZ3f10ImnX2oRByMxql3EimVqMysepbC9EeMUOpLwdy6Eoili2x6E4kf+ZUB5k/+Jv55alPfA==", + "dev": true, + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.38.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", + "integrity": "sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "dev": true, + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/crc32-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-shorthand-properties": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/css-shorthand-properties/-/css-shorthand-properties-1.1.1.tgz", + "integrity": "sha512-Md+Juc7M3uOdbAFwOYlTrccIZ7oCFuzrhKYQjdeUEW/sE1hv17Jp/Bws+ReOPpGVBTYCBoYo+G17V5Qo8QQ75A==", + "dev": true + }, + "node_modules/css-value": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/css-value/-/css-value-0.0.1.tgz", + "integrity": "sha512-FUV3xaJ63buRLgHrLQVlVgQnQdR4yqdLGaDu7g8CQcWjInDfM9plBTPI9FRfpahju1UBSaMckeb2/46ApS/V1Q==", + "dev": true + }, + "node_modules/custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", + "dev": true + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/date-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/decompress": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz", + "integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==", + "dev": true, + "dependencies": { + "decompress-tar": "^4.0.0", + "decompress-tarbz2": "^4.0.0", + "decompress-targz": "^4.0.0", + "decompress-unzip": "^4.0.1", + "graceful-fs": "^4.1.10", + "make-dir": "^1.0.0", + "pify": "^2.3.0", + "strip-dirs": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-tar": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", + "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", + "dev": true, + "dependencies": { + "file-type": "^5.2.0", + "is-stream": "^1.1.0", + "tar-stream": "^1.5.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tar/node_modules/file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", + "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", + "dev": true, + "dependencies": { + "decompress-tar": "^4.1.0", + "file-type": "^6.1.0", + "is-stream": "^1.1.0", + "seek-bzip": "^1.0.5", + "unbzip2-stream": "^1.0.9" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2/node_modules/file-type": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", + "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-targz": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", + "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", + "dev": true, + "dependencies": { + "decompress-tar": "^4.1.1", + "file-type": "^5.2.0", + "is-stream": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-targz/node_modules/file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-unzip": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", + "integrity": "sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==", + "dev": true, + "dependencies": { + "file-type": "^3.8.0", + "get-stream": "^2.2.0", + "pify": "^2.3.0", + "yauzl": "^2.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-unzip/node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-unzip/node_modules/get-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", + "integrity": "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==", + "dev": true, + "dependencies": { + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-unzip/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress/node_modules/make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress/node_modules/make-dir/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-eql": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.1.tgz", + "integrity": "sha512-nwQCf6ne2gez3o1MxWifqkciwt0zhl0LO1/UwVu4uMBuPmflWM4oQ70XMqHqnBJA+nhzncaqL9HVL6KkHJ28lw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true + }, + "node_modules/devtools": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-6.12.1.tgz", + "integrity": "sha512-JyG46suEiZmld7/UVeogkCWM0zYGt+2ML/TI+SkEp+bTv9cs46cDb0pKF3glYZJA7wVVL2gC07Ic0iCxyJEnCQ==", + "dev": true, + "dependencies": { + "@wdio/config": "6.12.1", + "@wdio/logger": "6.10.10", + "@wdio/protocols": "6.12.0", + "@wdio/utils": "6.11.0", + "chrome-launcher": "^0.13.1", + "edge-paths": "^2.1.0", + "puppeteer-core": "^5.1.0", + "ua-parser-js": "^0.7.21", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1299070", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1299070.tgz", + "integrity": "sha512-+qtL3eX50qsJ7c+qVyagqi7AWMoQCBGNfoyJZMwm/NSXVqLYbuitrWEEIzxfUmTNy7//Xe8yhMmQ+elj3uAqSg==", + "dev": true + }, + "node_modules/di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", + "dev": true + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", + "dev": true, + "dependencies": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/download": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/download/-/download-7.1.0.tgz", + "integrity": "sha512-xqnBTVd/E+GxJVrX5/eUJiLYjCGPwMpdL+jGhGU57BvtcA7wwhtHVbXBeUk51kOpW3S7Jn3BQbN9Q1R1Km2qDQ==", + "dev": true, + "dependencies": { + "archive-type": "^4.0.0", + "caw": "^2.0.1", + "content-disposition": "^0.5.2", + "decompress": "^4.2.0", + "ext-name": "^5.0.0", + "file-type": "^8.1.0", + "filenamify": "^2.0.0", + "get-stream": "^3.0.0", + "got": "^8.3.1", + "make-dir": "^1.2.0", + "p-event": "^2.1.0", + "pify": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/download/node_modules/@sindresorhus/is": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", + "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/download/node_modules/cacheable-request": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", + "integrity": "sha512-vag0O2LKZ/najSoUwDbVlnlCFvhBE/7mGTY2B5FgCBDcRD+oVV1HYTOwM6JZfMg/hIcM6IwnTZ1uQQL5/X3xIQ==", + "dev": true, + "dependencies": { + "clone-response": "1.0.2", + "get-stream": "3.0.0", + "http-cache-semantics": "3.8.1", + "keyv": "3.0.0", + "lowercase-keys": "1.0.0", + "normalize-url": "2.0.1", + "responselike": "1.0.2" + } + }, + "node_modules/download/node_modules/cacheable-request/node_modules/lowercase-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", + "integrity": "sha512-RPlX0+PHuvxVDZ7xX+EBVAp4RsVxP/TdDSN2mJYdiq1Lc4Hz7EUSjUI7RZrKKlmrIzVhf6Jo2stj7++gVarS0A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/download/node_modules/clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + } + }, + "node_modules/download/node_modules/decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/download/node_modules/got": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz", + "integrity": "sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^0.7.0", + "cacheable-request": "^2.1.1", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "into-stream": "^3.1.0", + "is-retry-allowed": "^1.1.0", + "isurl": "^1.0.0-alpha5", + "lowercase-keys": "^1.0.0", + "mimic-response": "^1.0.0", + "p-cancelable": "^0.4.0", + "p-timeout": "^2.0.1", + "pify": "^3.0.0", + "safe-buffer": "^5.1.1", + "timed-out": "^4.0.1", + "url-parse-lax": "^3.0.0", + "url-to-options": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/download/node_modules/http-cache-semantics": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", + "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", + "dev": true + }, + "node_modules/download/node_modules/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==", + "dev": true + }, + "node_modules/download/node_modules/keyv": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", + "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.0" + } + }, + "node_modules/download/node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/download/node_modules/make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/download/node_modules/normalize-url": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", + "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", + "dev": true, + "dependencies": { + "prepend-http": "^2.0.0", + "query-string": "^5.0.1", + "sort-keys": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/download/node_modules/p-cancelable": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", + "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/download/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/download/node_modules/responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", + "dev": true, + "dependencies": { + "lowercase-keys": "^1.0.0" + } + }, + "node_modules/download/node_modules/sort-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", + "integrity": "sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg==", + "dev": true, + "dependencies": { + "is-plain-obj": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/duplexer3": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", + "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==", + "dev": true + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/edge-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-2.2.1.tgz", + "integrity": "sha512-AI5fC7dfDmCdKo3m5y7PkYE8m6bMqR6pvVpgtrZkkhcJXFLelUgkjrhk3kXXx8Kbw2cRaTT4LkOR7hqf39KJdw==", + "dev": true, + "dependencies": { + "@types/which": "^1.3.2", + "which": "^2.0.2" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz", + "integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", + "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "dev": true, + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", + "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", + "dev": true + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/eslint": { + "version": "9.10.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz", + "integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.18.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.10.0", + "@eslint/plugin-kit": "^0.1.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.0", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.0.2", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.1.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-html": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-html/-/eslint-plugin-html-8.1.1.tgz", + "integrity": "sha512-6qmlJsc40D2m3Dn9oEH+0PAOkJhxVu0f5sVItqpCE0YWgYnyP4xCjBc3UWTHaJcY9ARkWOLIIuXLq0ndRnQOHw==", + "dev": true, + "dependencies": { + "htmlparser2": "^9.1.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", + "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", + "dev": true, + "dependencies": { + "acorn": "^8.12.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==", + "dev": true, + "dependencies": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa/node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "dev": true, + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "node_modules/execa/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/execa/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/execa/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + }, + "node_modules/executable": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", + "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "dev": true, + "dependencies": { + "pify": "^2.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/executable/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ext-list": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", + "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", + "dev": true, + "dependencies": { + "mime-db": "^1.28.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ext-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", + "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", + "dev": true, + "dependencies": { + "ext-list": "^2.0.0", + "sort-keys-length": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/file-type": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-8.1.0.tgz", + "integrity": "sha512-qyQ0pzAy78gVoJsmYeNgl8uH8yKhr1lVhW7JbzJmnlRi0I4R2eEDEJZVKG8agpDnLpacwNbDhLNG/LMdxHD2YQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/filenamify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-2.1.0.tgz", + "integrity": "sha512-ICw7NTT6RsDp2rnYKVd8Fu4cr6ITzGy3+u4vUujPkabyaz+03F24NWEX7fs5fp+kBonlaqPH8fAO2NM+SXt/JA==", + "dev": true, + "dependencies": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.0", + "trim-repeated": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/finalhandler/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-versions": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz", + "integrity": "sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==", + "dev": true, + "dependencies": { + "semver-regex": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-proxy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-2.1.0.tgz", + "integrity": "sha512-zmZIaQTWnNQb4R4fJUEp/FC51eZsc6EkErspy3xtIYStaq8EB/hDIWipxsal+E8rz0qD7f2sL/NA9Xee4RInJw==", + "dev": true, + "dependencies": { + "npm-conf": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/get-uri": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", + "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", + "dev": true, + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4", + "fs-extra": "^11.2.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/global-agent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-2.2.0.tgz", + "integrity": "sha512-+20KpaW6DDLqhG7JDiJpD1JvNvb8ts+TNl7BPOYcURqCrXqnN1Vf+XVOrkKJAFPqfX+oEhsdzOj1hLWkBTdNJg==", + "dev": true, + "dependencies": { + "boolean": "^3.0.1", + "core-js": "^3.6.5", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/global-agent/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/global-agent/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/global-agent/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/globals": { + "version": "15.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.9.0.tgz", + "integrity": "sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbol-support-x": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", + "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-to-string-tag-x": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", + "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", + "dev": true, + "dependencies": { + "has-symbol-support-x": "^1.4.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/header-case": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", + "integrity": "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==", + "dev": true, + "dependencies": { + "capital-case": "^1.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/html-minifier-terser": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", + "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==", + "dev": true, + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "~5.3.2", + "commander": "^10.0.0", + "entities": "^4.4.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.15.1" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + } + }, + "node_modules/htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-lazy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-3.1.0.tgz", + "integrity": "sha512-8/gvXvX2JMn0F+CDlSC4l6kOmVaLOO3XLkksI7CI3Ud95KDYJuYur2b9P/PUt/i/pDAMd/DulQsNbbbmRRsDIQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/into-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz", + "integrity": "sha512-TcdjPibTksa1NQximqep2r17ISRiNE9fwlfbg3F8ANdvP5/yrFTew86VcO//jk4QTaMlbjypPBq76HN2zaKfZQ==", + "dev": true, + "dependencies": { + "from2": "^2.1.1", + "p-is-promise": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-it-type": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/is-it-type/-/is-it-type-5.1.2.tgz", + "integrity": "sha512-q/gOZQTNYABAxaXWnBKZjTFH4yACvWEFtgVOj+LbgxYIgAJG1xVmUZOsECSrZPIemYUQvaQWVilSFVbh4Eyt8A==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.16.7", + "globalthis": "^1.0.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/is-natural-number": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", + "integrity": "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==", + "dev": true + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isurl": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", + "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", + "dev": true, + "dependencies": { + "has-to-string-tag-x": "^1.2.0", + "is-object": "^1.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/jackspeak": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz", + "integrity": "sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "dev": true, + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true + }, + "node_modules/jsdoc": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.3.tgz", + "integrity": "sha512-Nu7Sf35kXJ1MWDZIMAuATRQTg1iIPdzh7tqJ6jjvaU/GfDf+qi5UV8zJR3Mo+/pYFvm8mzay4+6O5EWigaQBQw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.15", + "@jsdoc/salty": "^0.2.1", + "@types/markdown-it": "^14.1.1", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^14.1.0", + "markdown-it-anchor": "^8.6.7", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "underscore": "~1.13.2" + }, + "bin": { + "jsdoc": "jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsdoc-tsimport-plugin": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/jsdoc-tsimport-plugin/-/jsdoc-tsimport-plugin-1.0.5.tgz", + "integrity": "sha512-6mvyF+tXdanf3zxEumTF9Uf/sXGlANP+XohSuiJiOVVWPGxi+3f2a2sy5Ew3W+0PMYUkcGYNxfYd5mMZsIHQpg==", + "dev": true + }, + "node_modules/jsdoc/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "peer": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/karma": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", + "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", + "dev": true, + "dependencies": { + "@colors/colors": "1.5.0", + "body-parser": "^1.19.0", + "braces": "^3.0.2", + "chokidar": "^3.5.1", + "connect": "^3.7.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.1", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.4.1", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.5", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^4.7.2", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" + }, + "bin": { + "karma": "bin/karma" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/karma-chai": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/karma-chai/-/karma-chai-0.1.0.tgz", + "integrity": "sha512-mqKCkHwzPMhgTYca10S90aCEX9+HjVjjrBFAsw36Zj7BlQNbokXXCAe6Ji04VUMsxcY5RLP7YphpfO06XOubdg==", + "dev": true, + "peerDependencies": { + "chai": "*", + "karma": ">=0.10.9" + } + }, + "node_modules/karma-mocha": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-2.0.1.tgz", + "integrity": "sha512-Tzd5HBjm8his2OA4bouAsATYEpZrp9vC7z5E5j4C5Of5Rrs1jY67RAwXNcVmd/Bnk1wgvQRou0zGVLey44G4tQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.3" + } + }, + "node_modules/karma-sauce-launcher": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/karma-sauce-launcher/-/karma-sauce-launcher-4.3.6.tgz", + "integrity": "sha512-Ej62q4mUPFktyAm8g0g8J5qhwEkXwdHrwtiV4pZjKNHNnSs+4qgDyzs3VkpOy3AmNTsTqQXUN/lpiy0tZpDJZQ==", + "dev": true, + "dependencies": { + "global-agent": "^2.1.12", + "saucelabs": "^4.6.3", + "webdriverio": "^6.7.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/karma-spec-reporter": { + "version": "0.0.36", + "resolved": "https://registry.npmjs.org/karma-spec-reporter/-/karma-spec-reporter-0.0.36.tgz", + "integrity": "sha512-11bvOl1x6ryKZph7kmbmMpbi8vsngEGxGOoeTlIcDaH3ab3j8aPJnZ+r+K/SS0sBSGy5VGkGYO2+hLct7hw/6w==", + "dev": true, + "dependencies": { + "colors": "1.4.0" + }, + "peerDependencies": { + "karma": ">=0.9" + } + }, + "node_modules/karma/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.9" + } + }, + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.11" + } + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lighthouse-logger": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", + "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", + "dev": true, + "dependencies": { + "debug": "^2.6.9", + "marky": "^1.2.2" + } + }, + "node_modules/lighthouse-logger/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/lighthouse-logger/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "dev": true + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "dev": true + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "dev": true + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true + }, + "node_modules/lodash.isobject": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", + "integrity": "sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA==", + "dev": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "dev": true + }, + "node_modules/lodash.zip": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.zip/-/lodash.zip-4.2.0.tgz", + "integrity": "sha512-C7IOaBBK/0gMORRBd8OETNx3kmOkgIWIPvyDpZSCTwUrpYmgZwJkjZeOD8ww4xbOUOs4/attY+pciKvadNfFbg==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/log-symbols/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log4js": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", + "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", + "dev": true, + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.5" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/loglevel": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz", + "integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, + "node_modules/loglevel-plugin-prefix": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/loglevel-plugin-prefix/-/loglevel-plugin-prefix-0.8.4.tgz", + "integrity": "sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==", + "dev": true + }, + "node_modules/loupe": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.0.tgz", + "integrity": "sha512-qKl+FrLXUhFuHUoDJG7f8P8gEMHq9NFS0c6ghXG1J0rldmZFQZoNVv/vyirE9qwCIhWZDsvEFd1sbFu3GvRQFg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it-anchor": { + "version": "8.6.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", + "dev": true, + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/marky": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", + "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==", + "dev": true + }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/matcher/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "node_modules/mocha": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz", + "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mocha/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-conf": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", + "integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==", + "dev": true, + "dependencies": { + "config-chain": "^1.1.11", + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-conf/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "dev": true, + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/os-filter-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/os-filter-obj/-/os-filter-obj-2.0.0.tgz", + "integrity": "sha512-uksVLsqG3pVdzzPvmAHpBK0wKxYItuzZr7SziusRPoz67tGV8rL1szZ6IdeUrbqLjGDwApBtN29eEE3IqGHOjg==", + "dev": true, + "dependencies": { + "arch": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-event": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-2.3.1.tgz", + "integrity": "sha512-NQCqOFhbpVTMX4qMe8PF8lbGtzZ+LCiN7pcNrb/413Na7+TRoe1xkKUzuWa/YEJdGQ0FvKtj35EEbDoVPO2kbA==", + "dev": true, + "dependencies": { + "p-timeout": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-is-promise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "integrity": "sha512-zL7VE4JVS2IFSkR2GQKDSPEVxkoH43/p7oEnwpdCndKYJO0HVeRB7fA8TJwuLOTBREtK0ea8eHaxdwcpob5dmg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", + "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", + "dev": true, + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.1.tgz", + "integrity": "sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A==", + "dev": true, + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "pac-resolver": "^7.0.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz", + "integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/platform": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", + "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==", + "dev": true + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, + "node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/puppeteer": { + "version": "22.11.2", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-22.11.2.tgz", + "integrity": "sha512-8fjdQSgW0sq7471ftca24J7sXK+jXZ7OW7Gx+NEBFNyXrcTiBfukEI46gNq6hiMhbLEDT30NeylK/1ZoPdlKSA==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@puppeteer/browsers": "2.2.3", + "cosmiconfig": "9.0.0", + "devtools-protocol": "0.0.1299070", + "puppeteer-core": "22.11.2" + }, + "bin": { + "puppeteer": "lib/esm/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-5.5.0.tgz", + "integrity": "sha512-tlA+1n+ziW/Db03hVV+bAecDKse8ihFRXYiEypBe9IlLRvOCzYFG6qrCMBYK34HO/Q/Ecjc+tvkHRAfLVH+NgQ==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "devtools-protocol": "0.0.818844", + "extract-zip": "^2.0.0", + "https-proxy-agent": "^4.0.0", + "node-fetch": "^2.6.1", + "pkg-dir": "^4.2.0", + "progress": "^2.0.1", + "proxy-from-env": "^1.0.0", + "rimraf": "^3.0.2", + "tar-fs": "^2.0.0", + "unbzip2-stream": "^1.3.3", + "ws": "^7.2.3" + }, + "engines": { + "node": ">=10.18.1" + } + }, + "node_modules/puppeteer-core/node_modules/agent-base": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", + "dev": true, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/puppeteer-core/node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/puppeteer-core/node_modules/devtools-protocol": { + "version": "0.0.818844", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.818844.tgz", + "integrity": "sha512-AD1hi7iVJ8OD0aMLQU5VK0XH9LDlA1+BcPIgrAxPfaibx2DbWucuyOhc4oyQCbnvDDO68nN6/LcKfqTP343Jjg==", + "dev": true + }, + "node_modules/puppeteer-core/node_modules/https-proxy-agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", + "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", + "dev": true, + "dependencies": { + "agent-base": "5", + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/puppeteer-core/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/puppeteer-core/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/puppeteer-core/node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/puppeteer-core/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/puppeteer-core/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/puppeteer/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/puppeteer/node_modules/puppeteer-core": { + "version": "22.11.2", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.11.2.tgz", + "integrity": "sha512-vQo+YDuePyvj+92Z9cdtxi/HalKf+k/R4tE80nGtQqJRNqU81eHaHkbVfnLszdaLlvwFF5tipnnSCzqWlEddtw==", + "dev": true, + "dependencies": { + "@puppeteer/browsers": "2.2.3", + "chromium-bidi": "0.5.23", + "debug": "4.3.5", + "devtools-protocol": "0.0.1299070", + "ws": "8.17.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "dev": true, + "engines": { + "node": ">=0.9" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/query-string": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", + "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", + "dev": true, + "dependencies": { + "decode-uri-component": "^0.2.0", + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dev": true, + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "node_modules/requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/resq": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/resq/-/resq-1.11.0.tgz", + "integrity": "sha512-G10EBz+zAAy3zUd/CDoBbXRL6ia9kOo3xRHrMDsHljI0GDkhYlyjwoCx5+3eCC4swi1uCoZQhskuJkj7Gp57Bw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^2.0.1" + } + }, + "node_modules/resq/node_modules/fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==", + "dev": true + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", + "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", + "dev": true + }, + "node_modules/rgb2hex": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.2.3.tgz", + "integrity": "sha512-clEe0m1xv+Tva1B/TOepuIcvLAxP0U+sCDfgt1SX1HmI2Ahr5/Cd/nzJM1e78NKVtWdoo0s33YehpFA8UfIShQ==", + "dev": true + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/rollup": { + "version": "2.79.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", + "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "dev": true, + "optional": true, + "peer": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/saucelabs": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-4.7.8.tgz", + "integrity": "sha512-K2qaRUixc7+8JiAwpTvEsIQVzzUkYwa0mAfs0akGagRlWXUR1JrsmgJRyz28qkwpERW1KDuByn3Ju96BuW1V7Q==", + "dev": true, + "dependencies": { + "bin-wrapper": "^4.1.0", + "change-case": "^4.1.1", + "form-data": "^3.0.0", + "got": "^11.7.0", + "hash.js": "^1.1.7", + "tunnel": "0.0.6", + "yargs": "^16.0.3" + }, + "bin": { + "sl": "bin/sl" + } + }, + "node_modules/seek-bzip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz", + "integrity": "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==", + "dev": true, + "dependencies": { + "commander": "^2.8.1" + }, + "bin": { + "seek-bunzip": "bin/seek-bunzip", + "seek-table": "bin/seek-bzip-table" + } + }, + "node_modules/seek-bzip/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true + }, + "node_modules/semver-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz", + "integrity": "sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/semver-truncate": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/semver-truncate/-/semver-truncate-1.1.2.tgz", + "integrity": "sha512-V1fGg9i4CL3qesB6U0L6XAm4xOJiHmt4QAacazumuasc03BvtFGIMCduv01JWQ69Nv+JST9TqhSCiJoxoY031w==", + "dev": true, + "dependencies": { + "semver": "^5.3.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/semver-truncate/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/sentence-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", + "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/showdown": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/showdown/-/showdown-2.1.0.tgz", + "integrity": "sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==", + "dev": true, + "dependencies": { + "commander": "^9.0.0" + }, + "bin": { + "showdown": "bin/showdown.js" + }, + "funding": { + "type": "individual", + "url": "https://www.paypal.me/tiviesantos" + } + }, + "node_modules/showdown/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/simple-invariant": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/simple-invariant/-/simple-invariant-2.0.1.tgz", + "integrity": "sha512-1sbhsxqI+I2tqlmjbz99GXNmZtr6tKIyEgGGnJw/MKGblalqk/XoOYYFJlBzTKZCxx8kLaD3FD5s9BEEjx5Pyg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/smob": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz", + "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==", + "dev": true + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/socket.io": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", + "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "dev": true, + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dev": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "dev": true, + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz", + "integrity": "sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==", + "dev": true, + "dependencies": { + "is-plain-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-keys-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", + "integrity": "sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==", + "dev": true, + "dependencies": { + "sort-keys": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/streamroller": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", + "dev": true, + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/streamroller/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/streamroller/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/streamroller/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/streamx": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz", + "integrity": "sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==", + "dev": true, + "dependencies": { + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", + "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", + "dev": true, + "dependencies": { + "is-natural-number": "^4.0.1" + } + }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tar-fs": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.5.tgz", + "integrity": "sha512-JOgGAmZyMgbqpLwct7ZV8VzkEB6pxXFBVErLtb+XCOqzc6w1xiWKI9GVd6bwk68EX7eJ4DWmfXVmq8K2ziZTGg==", + "dev": true, + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" + } + }, + "node_modules/tar-fs/node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "dev": true, + "dependencies": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/terser": { + "version": "5.29.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.29.2.tgz", + "integrity": "sha512-ZiGkhUBIM+7LwkNjXYJq8svgkd+QK3UUr0wJqY4MieaezBSAIPgbSPZyIx0idM6XWK5CMzSWa8MJIzmRcB8Caw==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", + "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/text-decoder": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.0.tgz", + "integrity": "sha512-TmLJNj6UgX8xcUZo4UDStGQtDiTzF7BzWlzn9g7UWrjkpHr5uJTK1ld16wZ3LXb2vb6jH8qU89dW5whuMdXYdw==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "node_modules/timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/to-buffer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", + "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "node_modules/trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ua-parser-js": { + "version": "0.7.37", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.37.tgz", + "integrity": "sha512-xV8kqRKM+jhMvcHWUKthV9fNebIzrNy//2O9ZwWcfiBFR5f25XVZPLlEajk/sf3Ra15V92isyQqnIEXRDaZWEA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "engines": { + "node": "*" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", + "dev": true + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/universal-user-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", + "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==", + "dev": true + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/upper-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", + "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/upper-case-first": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", + "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", + "dev": true, + "dependencies": { + "prepend-http": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/url-to-options": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", + "integrity": "sha512-0kQLIzG4fdk/G5NONku64rSH/x32NOA39LVQqlK8Le6lvTF6GGRJpqaQFGgU+CLwySIqBSMdwYM0sYcW9f6P4A==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", + "dev": true + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", + "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/webdriver": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-6.12.1.tgz", + "integrity": "sha512-3rZgAj9o2XHp16FDTzvUYaHelPMSPbO1TpLIMUT06DfdZjNYIzZiItpIb/NbQDTPmNhzd9cuGmdI56WFBGY2BA==", + "dev": true, + "dependencies": { + "@wdio/config": "6.12.1", + "@wdio/logger": "6.10.10", + "@wdio/protocols": "6.12.0", + "@wdio/utils": "6.11.0", + "got": "^11.0.2", + "lodash.merge": "^4.6.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webdriverio": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-6.12.1.tgz", + "integrity": "sha512-Nx7ge0vTWHVIRUbZCT+IuMwB5Q0Q5nLlYdgnmmJviUKLuc3XtaEBkYPTbhHWHgSBXsPZMIc023vZKNkn+6iyeQ==", + "dev": true, + "dependencies": { + "@types/puppeteer-core": "^5.4.0", + "@wdio/config": "6.12.1", + "@wdio/logger": "6.10.10", + "@wdio/repl": "6.11.0", + "@wdio/utils": "6.11.0", + "archiver": "^5.0.0", + "atob": "^2.1.2", + "css-shorthand-properties": "^1.1.1", + "css-value": "^0.0.1", + "devtools": "6.12.1", + "fs-extra": "^9.0.1", + "get-port": "^5.1.1", + "grapheme-splitter": "^1.0.2", + "lodash.clonedeep": "^4.5.0", + "lodash.isobject": "^3.0.2", + "lodash.isplainobject": "^4.0.6", + "lodash.zip": "^4.2.0", + "minimatch": "^3.0.4", + "puppeteer-core": "^5.1.0", + "resq": "^1.9.1", + "rgb2hex": "0.2.3", + "serialize-error": "^8.0.0", + "webdriver": "6.12.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webdriverio/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/webdriverio/node_modules/serialize-error": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", + "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webdriverio/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/workerpool": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "dev": true + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yauzl-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yauzl-promise/-/yauzl-promise-4.0.0.tgz", + "integrity": "sha512-/HCXpyHXJQQHvFq9noqrjfa/WpQC2XYs3vI7tBiAi4QiIU1knvYhZGaO1QPjwIVMdqflxbmwgMXtYeaRiAE0CA==", + "dev": true, + "dependencies": { + "@node-rs/crc32": "^1.7.0", + "is-it-type": "^5.1.2", + "simple-invariant": "^2.0.1" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "dev": true, + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "dev": true, + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..0b80e9a --- /dev/null +++ b/package.json @@ -0,0 +1,104 @@ +{ + "name": "ical.js", + "version": "2.1.0", + "author": "Philipp Kewisch", + "contributors": [ + "Github Contributors (https://github.com/kewisch/ical.js/graphs/contributors)" + ], + "description": "Javascript parser for ics (rfc5545) and vcard (rfc6350) data", + "main": "dist/ical.js", + "types": "dist/types/module.d.ts", + "type": "module", + "repository": { + "type": "git", + "url": "https://github.com/kewisch/ical.js.git" + }, + "keywords": [ + "calendar", + "iCalendar", + "jCal", + "vCard", + "jCard", + "parser" + ], + "devDependencies": { + "@babel/preset-env": "^7.24.3", + "@eslint/js": "^9.0.0", + "@octokit/core": "^6.0.1", + "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-terser": "^0.4.4", + "@rollup/plugin-typescript": "^11.1.6", + "@stylistic/eslint-plugin": "^2.1.0", + "benchmark": "^2.1.4", + "c8": "^10.1.2", + "chai": "^5.1.0", + "clean-jsdoc-theme": "^4.2.18", + "eslint": "^9.0.0", + "eslint-plugin-html": "^8.1.1", + "globals": "^15.0.0", + "jsdoc": "^4.0.2", + "jsdoc-tsimport-plugin": "^1.0.5", + "karma": "^6.4.3", + "karma-chai": "^0.1.0", + "karma-mocha": "^2.0.1", + "karma-sauce-launcher": "^4.3.6", + "karma-spec-reporter": "^0.0.36", + "mocha": "^10.3.0", + "node-fetch": "^3.3.2", + "yauzl-promise": "^4.0.0" + }, + "license": "MPL-2.0", + "engine": { + "node": ">=10" + }, + "scripts": { + "test": "npm run test-unit && npm run test-acceptance", + "test-unit": "c8 mocha", + "test-acceptance": "mocha test/acceptance/*_test.js", + "test-performance": "mocha --reporter test/support/perfReporter.cjs test/performance/*_test.js", + "test-browser": "karma start karma.conf.cjs", + "test-all": "npm run test-unit && npm run test-acceptance && npm run test-performance && npm run test-browser", + "build": "rollup -c", + "lint": "eslint", + "jsdoc": "rm -rf docs/api && jsdoc --configure jsdoc-prepare.json && rm -rf docs/api && jsdoc --configure jsdoc.json --verbose", + "validator": "node tools/scriptutils.js replace-unpkg tools/validator.html docs/validator.html", + "recurtester": "node tools/scriptutils.js replace-unpkg tools/recur-tester.html docs/recur-tester.html", + "ghpages": "npm run jsdoc && npm run validator && npm run recurtester" + }, + "exports": { + "import": "./dist/ical.js", + "require": "./dist/ical.es5.cjs", + "types": "./dist/types/module.d.ts" + }, + "files": [ + "dist/ical.js", + "dist/ical.min.js", + "dist/ical.es5.cjs", + "dist/ical.es5.min.cjs", + "dist/types/*", + "lib/ical/*.js" + ], + "mocha": { + "ui": "tdd", + "require": "test/support/helper.js", + "reporter": "spec" + }, + "c8": { + "include": "lib/ical", + "reporter": [ + "text", + "html", + "lcov" + ] + }, + "saucelabs": { + "SL_Chrome": { + "base": "SauceLabs", + "browserName": "chrome" + }, + "SL_Firefox": { + "base": "SauceLabs", + "browserName": "firefox" + } + } +} diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..b107fd9 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,67 @@ +import { babel } from '@rollup/plugin-babel'; +import terser from '@rollup/plugin-terser'; +import typescript from '@rollup/plugin-typescript'; + +const LICENSE = +`/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */`; + +const TERSER_OPTIONS = { + format: { + comments: function(node, comment) { + if (comment.type == 'comment2') { + return /terms of the Mozilla Public/.test(comment.value) && comment.pos === 0; + } + return false; + } + } +}; + +export default [{ + input: 'lib/ical/module.js', + output: [ + { file: 'dist/ical.js', format: 'es', exports: 'default' }, + { + file: 'dist/ical.min.js', + banner: LICENSE, + format: 'es', + exports: 'default', + plugins: [terser(TERSER_OPTIONS)] + } + ] +}, { + input: 'lib/ical/module.js', + output: [ + { + file: 'dist/ical.es5.cjs', + exports: 'default', + name: 'ICAL', + format: 'umd', + banner: LICENSE, + }, + { + file: 'dist/ical.es5.min.cjs', + exports: 'default', + name: 'ICAL', + format: 'umd', + banner: LICENSE, + plugins: [terser(TERSER_OPTIONS)], + } + ], + plugins: [ + babel({ babelHelpers: 'bundled', presets: ['@babel/preset-env'] }), + typescript({ + include: ['lib/ical/*.js'], + noForceEmit: true, + compilerOptions: { + allowJs: true, + declaration: true, + emitDeclarationOnly: true, + declarationMap: true, + declarationDir: 'dist/types', + }, + }) + ] +}]; diff --git a/samples/blank_description.ics b/samples/blank_description.ics new file mode 100644 index 0000000..568d7e0 --- /dev/null +++ b/samples/blank_description.ics @@ -0,0 +1,51 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +X-WR-CALNAME:calmozilla1@gmail.com +X-WR-TIMEZONE:America/Los_Angeles +BEGIN:VTIMEZONE +TZID:America/Los_Angeles +X-LIC-LOCATION:America/Los_Angeles +BEGIN:DAYLIGHT +TZOFFSETFROM:-0800 +TZOFFSETTO:-0700 +TZNAME:PDT +DTSTART:19700308T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0700 +TZOFFSETTO:-0800 +TZNAME:PST +DTSTART:19701101T020000 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTART;TZID=America/Los_Angeles:20120630T060000 +DTEND;TZID=America/Los_Angeles:20120630T070000 +DTSTAMP:20120724T212411Z +UID:dn4vrfmfn5p05roahsopg57h48@google.com +CREATED:20120724T212411Z +DESCRIPTION: +LAST-MODIFIED:20120724T212411Z +LOCATION: +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Really long event name thing +TRANSP:OPAQUE +BEGIN:VALARM +ACTION:EMAIL +DESCRIPTION:This is an event reminder +SUMMARY:Alarm notification +ATTENDEE:mailto:calmozilla1@gmail.com +TRIGGER:-P0DT0H30M0S +END:VALARM +BEGIN:VALARM +ACTION:DISPLAY +DESCRIPTION:This is an event reminder +TRIGGER:-P0DT0H30M0S +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/samples/blank_line_end.ics b/samples/blank_line_end.ics new file mode 100644 index 0000000..2c397c2 --- /dev/null +++ b/samples/blank_line_end.ics @@ -0,0 +1,4 @@ +BEGIN:VCALENDAR +END:VCALENDAR + + diff --git a/samples/blank_line_mid.ics b/samples/blank_line_mid.ics new file mode 100644 index 0000000..5e54e2e --- /dev/null +++ b/samples/blank_line_mid.ics @@ -0,0 +1,4 @@ +BEGIN:VCALENDAR +COMMENT:This blank line is invalid + +END:VCALENDAR diff --git a/samples/daily_recur.ics b/samples/daily_recur.ics new file mode 100644 index 0000000..9983098 --- /dev/null +++ b/samples/daily_recur.ics @@ -0,0 +1,52 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +X-WR-CALNAME:calmozilla1@gmail.com +X-WR-TIMEZONE:America/Los_Angeles +BEGIN:VTIMEZONE +TZID:America/Los_Angeles +X-LIC-LOCATION:America/Los_Angeles +BEGIN:DAYLIGHT +TZOFFSETFROM:-0800 +TZOFFSETTO:-0700 +TZNAME:PDT +DTSTART:19700308T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0700 +TZOFFSETTO:-0800 +TZNAME:PST +DTSTART:19701101T020000 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTART;TZID=America/Los_Angeles:20120801T050000 +DTEND;TZID=America/Los_Angeles:20120801T060000 +RRULE:FREQ=DAILY +DTSTAMP:20120803T221236Z +UID:tgh9qho17b07pk2n2ji3gluans@google.com +CREATED:20120803T221236Z +DESCRIPTION: +LAST-MODIFIED:20120803T221236Z +LOCATION: +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Every day recurring +TRANSP:OPAQUE +BEGIN:VALARM +ACTION:EMAIL +DESCRIPTION:This is an event reminder +SUMMARY:Alarm notification +ATTENDEE:mailto:calmozilla1@gmail.com +TRIGGER:-P0DT0H30M0S +END:VALARM +BEGIN:VALARM +ACTION:DISPLAY +DESCRIPTION:This is an event reminder +TRIGGER:-P0DT0H30M0S +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/samples/day_long_recur_yearly.ics b/samples/day_long_recur_yearly.ics new file mode 100644 index 0000000..0c0aeb5 --- /dev/null +++ b/samples/day_long_recur_yearly.ics @@ -0,0 +1,52 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +X-WR-CALNAME:calmozilla1@gmail.com +X-WR-TIMEZONE:America/Los_Angeles +BEGIN:VTIMEZONE +TZID:America/Los_Angeles +X-LIC-LOCATION:America/Los_Angeles +BEGIN:DAYLIGHT +TZOFFSETFROM:-0800 +TZOFFSETTO:-0700 +TZNAME:PDT +DTSTART:19700308T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0700 +TZOFFSETTO:-0800 +TZNAME:PST +DTSTART:19701101T020000 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTART;VALUE=DATE:20120803 +DTEND;VALUE=DATE:20120804 +RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR +DTSTAMP:20120803T221306Z +UID:4pfh824gvims850j0gar361t04@google.com +CREATED:20120803T221306Z +DESCRIPTION: +LAST-MODIFIED:20120803T221306Z +LOCATION: +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Day Long Event +TRANSP:TRANSPARENT +BEGIN:VALARM +ACTION:EMAIL +DESCRIPTION:This is an event reminder +SUMMARY:Alarm notification +ATTENDEE:mailto:calmozilla1@gmail.com +TRIGGER;VALUE=DATE-TIME:20120802T233000Z +END:VALARM +BEGIN:VALARM +ACTION:DISPLAY +DESCRIPTION:This is an event reminder +TRIGGER;VALUE=DATE-TIME:20120802T233000Z +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/samples/duration_instead_of_dtend.ics b/samples/duration_instead_of_dtend.ics new file mode 100644 index 0000000..8f4fc27 --- /dev/null +++ b/samples/duration_instead_of_dtend.ics @@ -0,0 +1,39 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +X-WR-CALNAME:calmozilla1@example.com +X-WR-TIMEZONE:America/Los_Angeles +BEGIN:VTIMEZONE +TZID:America/Los_Angeles +X-LIC-LOCATION:America/Los_Angeles +BEGIN:DAYLIGHT +TZOFFSETFROM:-0800 +TZOFFSETTO:-0700 +TZNAME:PDT +DTSTART:19700308T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0700 +TZOFFSETTO:-0800 +TZNAME:PST +DTSTART:19701101T020000 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTART;TZID=America/Los_Angeles:20120630T060000 +DURATION:P1D +DTSTAMP:20120724T212411Z +UID:dn4vrfmfn5p05roahsopg57h48@example.com +CREATED:20120724T212411Z +DESCRIPTION: +LAST-MODIFIED:20120724T212411Z +LOCATION: +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Really long event name thing +TRANSP:OPAQUE +END:VEVENT +END:VCALENDAR diff --git a/samples/forced_types.ics b/samples/forced_types.ics new file mode 100644 index 0000000..f213f01 --- /dev/null +++ b/samples/forced_types.ics @@ -0,0 +1,50 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +X-WR-CALNAME:calmozilla1@gmail.com +X-WR-TIMEZONE:America/Los_Angeles +BEGIN:VTIMEZONE +TZID:America/Los_Angeles +X-LIC-LOCATION:America/Los_Angeles +BEGIN:DAYLIGHT +TZOFFSETFROM:-0800 +TZOFFSETTO:-0700 +TZNAME:PDT +DTSTART:19700308T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0700 +TZOFFSETTO:-0800 +TZNAME:PST +DTSTART:19701101T020000 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTART;VALUE=DATE:20120904 +DTEND;VALUE=DATE:20120905 +DTSTAMP:20120905T084734Z +UID:redgrb1l0aju5edm6h0s102eu4@google.com +CREATED:20120905T084734Z +DESCRIPTION: +LAST-MODIFIED:20120905T084734Z +LOCATION: +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Event +TRANSP:TRANSPARENT +BEGIN:VALARM +ACTION:EMAIL +DESCRIPTION:This is an event reminder +SUMMARY:Alarm notification +ATTENDEE:mailto:calmozilla1@gmail.com +TRIGGER;VALUE=DATE-TIME:20120903T233000Z +END:VALARM +BEGIN:VALARM +ACTION:DISPLAY +DESCRIPTION:This is an event reminder +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/samples/google_birthday.ics b/samples/google_birthday.ics new file mode 100644 index 0000000..f9b7c9d --- /dev/null +++ b/samples/google_birthday.ics @@ -0,0 +1,90 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +X-WR-CALNAME:Contacts' birthdays and events +X-WR-TIMEZONE:America/Los_Angeles +X-WR-CALDESC:Your contacts' birthdays and anniversaries +BEGIN:VEVENT +DTSTART;VALUE=DATE:20141210 +DTEND;VALUE=DATE:20141211 +RRULE:FREQ=DAILY;INTERVAL=1;COUNT=1 +RDATE:20131210Z +RDATE:20121210Z +DTSTAMP:20121207T183041Z +UID:2014_BIRTHDAY_79d389868f96182e@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Contac + ts;X-NUM-GUESTS=0:mailto:4dhmurjkc5hn8sq0ctp6utbg5pr2sor1dhimsp31e8n6errfct + m6abj3dtmg@virtual +CLASS:PUBLIC +CREATED:20121207T183041Z +LAST-MODIFIED:20121207T183041Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:PErson #2's birthday +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20121210 +DTEND;VALUE=DATE:20121211 +DTSTAMP:20121207T183041Z +UID:BIRTHDAY_79d389868f96182e@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Contac + ts;X-NUM-GUESTS=0:mailto:4dhmurjkc5hn8sq0ctp6utbg5pr2sor1dhimsp31e8n6errfct + m6abj3dtmg@virtual +X-GOOGLE-CALENDAR-CONTENT-ICON:https://calendar.google.com/googlecalendar/i + mages/cake.gif +X-GOOGLE-CALENDAR-CONTENT-DISPLAY:chip +RECURRENCE-ID;VALUE=DATE:20121210 +CLASS:PUBLIC +CREATED:20121207T183041Z +DESCRIPTION:Today is PErson #2's birthday! +LAST-MODIFIED:20121207T183041Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:PErson #2's birthday +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20131210 +DTEND;VALUE=DATE:20131211 +DTSTAMP:20121207T183041Z +UID:BIRTHDAY_79d389868f96182e@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Contac + ts;X-NUM-GUESTS=0:mailto:4dhmurjkc5hn8sq0ctp6utbg5pr2sor1dhimsp31e8n6errfct + m6abj3dtmg@virtual +X-GOOGLE-CALENDAR-CONTENT-ICON:https://calendar.google.com/googlecalendar/i + mages/cake.gif +X-GOOGLE-CALENDAR-CONTENT-DISPLAY:chip +RECURRENCE-ID;VALUE=DATE:20131210 +CLASS:PUBLIC +CREATED:20121207T183041Z +DESCRIPTION:Today is PErson #2's birthday! +LAST-MODIFIED:20121207T183041Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:PErson #2's birthday +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20141210 +DTEND;VALUE=DATE:20141211 +DTSTAMP:20121207T183041Z +UID:BIRTHDAY_79d389868f96182e@google.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Contac + ts;X-NUM-GUESTS=0:mailto:4dhmurjkc5hn8sq0ctp6utbg5pr2sor1dhimsp31e8n6errfct + m6abj3dtmg@virtual +X-GOOGLE-CALENDAR-CONTENT-ICON:https://calendar.google.com/googlecalendar/i + mages/cake.gif +X-GOOGLE-CALENDAR-CONTENT-DISPLAY:chip +RECURRENCE-ID;VALUE=DATE:20141210 +CLASS:PUBLIC +CREATED:20121207T183041Z +DESCRIPTION:Today is PErson #2's birthday! +LAST-MODIFIED:20121207T183041Z +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:PErson #2's birthday +TRANSP:OPAQUE +END:VEVENT +END:VCALENDAR diff --git a/samples/minimal.ics b/samples/minimal.ics new file mode 100644 index 0000000..b97d2ac --- /dev/null +++ b/samples/minimal.ics @@ -0,0 +1,39 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +X-WR-CALNAME:calmozilla1@gmail.com +X-WR-TIMEZONE:America/Los_Angeles +BEGIN:VTIMEZONE +TZID:America/Los_Angeles +X-LIC-LOCATION:America/Los_Angeles +BEGIN:DAYLIGHT +TZOFFSETFROM:-0800 +TZOFFSETTO:-0700 +TZNAME:PDT +DTSTART:19700308T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0700 +TZOFFSETTO:-0800 +TZNAME:PST +DTSTART:19701101T020000 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTART;TZID=America/Los_Angeles:20120630T060000 +DTEND;TZID=America/Los_Angeles:20120630T070000 +DTSTAMP:20120724T212411Z +UID:dn4vrfmfn5p05roahsopg57h48@google.com +CREATED:20120724T212411Z +DESCRIPTION: +LAST-MODIFIED:20120724T212411Z +LOCATION: +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Really long event name thing +TRANSP:OPAQUE +END:VEVENT +END:VCALENDAR diff --git a/samples/multiple_rrules.ics b/samples/multiple_rrules.ics new file mode 100644 index 0000000..339c318 --- /dev/null +++ b/samples/multiple_rrules.ics @@ -0,0 +1,45 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:Zimbra-Calendar-Provider +BEGIN:VTIMEZONE +TZID:America/Los_Angeles +BEGIN:STANDARD +DTSTART:19710101T020000 +TZOFFSETTO:-0800 +TZOFFSETFROM:-0700 +RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=11;BYDAY=1SU +TZNAME:PST +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:19710101T020000 +TZOFFSETTO:-0700 +TZOFFSETFROM:-0800 +RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=2SU +TZNAME:PDT +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +UID:1334F9B7-6136-444E-A58D-472564C6AA73 +RRULE:FREQ=WEEKLY;UNTIL=20120730T065959Z +RRULE:FREQ=MONTHLY;BYDAY=SU;UNTIL=20120730T065959Z +SUMMARY:sahaja <> frashed +DESCRIPTION:weekly 1on1 +ATTENDEE;CN=James Lal;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS + -ACTION;RSVP=TRUE:mailto:jlal@mozilla.com +ORGANIZER;CN=Faramarz Rashed:mailto:frashed@mozilla.com +DTSTART;TZID=America/Los_Angeles:20120326T110000 +DTEND;TZID=America/Los_Angeles:20120326T113000 +STATUS:CONFIRMED +CLASS:PUBLIC +TRANSP:OPAQUE +LAST-MODIFIED:20120326T161522Z +DTSTAMP:20120730T165637Z +SEQUENCE:9 +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER;RELATED=START:-PT5M +DESCRIPTION:Reminder +END:VALARM +END:VEVENT +END:VCALENDAR + diff --git a/samples/only_dtstart_date.ics b/samples/only_dtstart_date.ics new file mode 100644 index 0000000..37a32dd --- /dev/null +++ b/samples/only_dtstart_date.ics @@ -0,0 +1,38 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +X-WR-CALNAME:calmozilla1@example.com +X-WR-TIMEZONE:America/Los_Angeles +BEGIN:VTIMEZONE +TZID:America/Los_Angeles +X-LIC-LOCATION:America/Los_Angeles +BEGIN:DAYLIGHT +TZOFFSETFROM:-0800 +TZOFFSETTO:-0700 +TZNAME:PDT +DTSTART:19700308T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0700 +TZOFFSETTO:-0800 +TZNAME:PST +DTSTART:19701101T020000 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTART;VALUE=DATE:20120630 +DTSTAMP:20120724T212411Z +UID:dn4vrfmfn5p05roahsopg57h48@example.com +CREATED:20120724T212411Z +DESCRIPTION: +LAST-MODIFIED:20120724T212411Z +LOCATION: +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Really long event name thing +TRANSP:OPAQUE +END:VEVENT +END:VCALENDAR diff --git a/samples/only_dtstart_time.ics b/samples/only_dtstart_time.ics new file mode 100644 index 0000000..fa6e9f6 --- /dev/null +++ b/samples/only_dtstart_time.ics @@ -0,0 +1,38 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +X-WR-CALNAME:calmozilla1@example.com +X-WR-TIMEZONE:America/Los_Angeles +BEGIN:VTIMEZONE +TZID:America/Los_Angeles +X-LIC-LOCATION:America/Los_Angeles +BEGIN:DAYLIGHT +TZOFFSETFROM:-0800 +TZOFFSETTO:-0700 +TZNAME:PDT +DTSTART:19700308T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0700 +TZOFFSETTO:-0800 +TZNAME:PST +DTSTART:19701101T020000 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTART;TZID=America/Los_Angeles:20120630T060000 +DTSTAMP:20120724T212411Z +UID:dn4vrfmfn5p05roahsopg57h48@example.com +CREATED:20120724T212411Z +DESCRIPTION: +LAST-MODIFIED:20120724T212411Z +LOCATION: +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Really long event name thing +TRANSP:OPAQUE +END:VEVENT +END:VCALENDAR \ No newline at end of file diff --git a/samples/parserv2.ics b/samples/parserv2.ics new file mode 100644 index 0000000..11c8301 --- /dev/null +++ b/samples/parserv2.ics @@ -0,0 +1,45 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:Zimbra-Calendar-Provider +BEGIN:VTIMEZONE +TZID:America/Los_Angeles +BEGIN:STANDARD +DTSTART:19710101T020000 +TZOFFSETTO:-0800 +TZOFFSETFROM:-0700 +RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11 +TZNAME:PST +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:19710101T020000 +TZOFFSETTO:-0700 +TZOFFSETFROM:-0800 +RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3 +TZNAME:PDT +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +UID:44c10eaa-db0b-4223-8653-cf2b63f26326 +RRULE:FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR +SUMMARY:Calendar +DESCRIPTION:desc +ATTENDEE;CN=XXX;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=TRU + E:mailto:foo@bar.com +ATTENDEE;CN=XXXX;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=TR + UE:mailto:x@bar.com +ORGANIZER;CN=foobar:mailto:x@bar.com +DTSTART;TZID=America/Los_Angeles:20120911T103000 +DTEND;TZID=America/Los_Angeles:20120911T110000 +STATUS:CONFIRMED +CLASS:PUBLIC +TRANSP:OPAQUE +LAST-MODIFIED:20120911T184851Z +DTSTAMP:20120911T184851Z +SEQUENCE:1 +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER;RELATED=START:-PT5M +DESCRIPTION:Reminder +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/samples/recur_instances.ics b/samples/recur_instances.ics new file mode 100644 index 0000000..833f29b --- /dev/null +++ b/samples/recur_instances.ics @@ -0,0 +1,98 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:Zimbra-Calendar-Provider +BEGIN:VTIMEZONE +TZID:America/Los_Angeles +BEGIN:STANDARD +DTSTART:19710101T020000 +TZOFFSETTO:-0800 +TZOFFSETFROM:-0700 +RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=11;BYDAY=1SU +TZNAME:PST +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:19710101T020000 +TZOFFSETTO:-0700 +TZOFFSETFROM:-0800 +RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=2SU +TZNAME:PDT +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VTIMEZONE +X-INVALID-TIMEZONE:TRUE +END:VTIMEZONE +BEGIN:VEVENT +UID:623c13c0-6c2b-45d6-a12b-c33ad61c4868 +DESCRIPTION:IAM FOO +RRULE:FREQ=MONTHLY;INTERVAL=1;BYDAY=1TU +SUMMARY:Crazy Event Thingy! +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Sahaja + Lal;X-NUM-GUESTS=0:mailto:calmozilla1@gmail.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=ja + mes@lightsofapollo.com;X-NUM-GUESTS=0:mailto:james@lightsofapollo.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=ia + m.revelation@gmail.com;X-NUM-GUESTS=0:mailto:iam.revelation@gmail.com +LOCATION:PLACE +ORGANIZER;CN=James Lal:mailto:jlal@mozilla.com +DTSTART;TZID=America/Los_Angeles:20121002T100000 +DTEND;TZID=America/Los_Angeles:20121002T103000 +STATUS:CONFIRMED +COLOR:red +CLASS:PUBLIC +TRANSP:OPAQUE +LAST-MODIFIED:20120912T171506Z +DTSTAMP:20120912T171506Z +SEQUENCE:0 +RDATE;TZID=America/Los_Angeles:20121105T100000 +RDATE;TZID=America/Los_Angeles:20121110T100000,20121130T100000 +EXDATE;TZID=America/Los_Angeles:20130402T100000 +EXDATE;TZID=America/Los_Angeles:20121204T100000 +EXDATE;TZID=America/Los_Angeles:20130205T100000 +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER;RELATED=START:-PT5M +DESCRIPTION:Reminder +END:VALARM +END:VEVENT +BEGIN:VEVENT +UID:623c13c0-6c2b-45d6-a12b-c33ad61c4868 +SUMMARY:Crazy Event Thingy! +DESCRIPTION:I HAZ CHANGED! +ORGANIZER;CN=James Lal:mailto:jlal@mozilla.com +DTSTART;TZID=America/Los_Angeles:20121002T150000 +DTEND;TZID=America/Los_Angeles:20121002T153000 +STATUS:CONFIRMED +CLASS:PUBLIC +TRANSP:OPAQUE +RECURRENCE-ID;TZID=America/Los_Angeles:20121002T100000 +LAST-MODIFIED:20120912T171540Z +DTSTAMP:20120912T171540Z +SEQUENCE:1 +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER;RELATED=START:-PT5M +DESCRIPTION:Reminder +END:VALARM +END:VEVENT +BEGIN:VEVENT +UID:623c13c0-6c2b-45d6-a12b-c33ad61c4868 +SUMMARY:Crazy Event Thingy! +ORGANIZER;CN=James Lal:mailto:jlal@mozilla.com +DTSTART;TZID=America/Los_Angeles:20121106T200000 +DTEND;TZID=America/Los_Angeles:20121106T203000 +STATUS:CONFIRMED +CLASS:PUBLIC +TRANSP:OPAQUE +RECURRENCE-ID:20121105T180000Z +LAST-MODIFIED:20120912T171820Z +DTSTAMP:20120912T171820Z +SEQUENCE:1 +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER;RELATED=START:-PT5M +DESCRIPTION:Reminder +END:VALARM +END:VEVENT +BEGIN:X-UNKNOWN +END:X-UNKNOWN +END:VCALENDAR diff --git a/samples/recur_instances_finite.ics b/samples/recur_instances_finite.ics new file mode 100644 index 0000000..e7dc481 --- /dev/null +++ b/samples/recur_instances_finite.ics @@ -0,0 +1,50 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:Zimbra-Calendar-Provider +BEGIN:VTIMEZONE +TZID:America/Los_Angeles +BEGIN:STANDARD +DTSTART:19710101T020000 +TZOFFSETTO:-0800 +TZOFFSETFROM:-0700 +RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=11;BYDAY=1SU +TZNAME:PST +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:19710101T020000 +TZOFFSETTO:-0700 +TZOFFSETFROM:-0800 +RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=2SU +TZNAME:PDT +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +UID:623c13c0-6c2b-45d6-a12b-c33ad61c4868 +DESCRIPTION:IAM FOO +RRULE:FREQ=MONTHLY;INTERVAL=1;BYDAY=1TU;UNTIL=20121231T100000 +SUMMARY:Crazy Event Thingy! +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Sahaja + Lal;X-NUM-GUESTS=0:mailto:calmozilla1@gmail.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=ja + mes@lightsofapollo.com;X-NUM-GUESTS=0:mailto:james@lightsofapollo.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=ia + m.revelation@gmail.com;X-NUM-GUESTS=0:mailto:iam.revelation@gmail.com +LOCATION:PLACE +ORGANIZER;CN=James Lal:mailto:jlal@mozilla.com +DTSTART;TZID=America/Los_Angeles:20121002T100000 +DTEND;TZID=America/Los_Angeles:20121002T103000 +STATUS:CONFIRMED +CLASS:PUBLIC +TRANSP:OPAQUE +LAST-MODIFIED:20120912T171506Z +DTSTAMP:20120912T171506Z +SEQUENCE:0 +RDATE;TZID=America/Los_Angeles:20121110T100000 +RDATE;TZID=America/Los_Angeles:20121105T100000 +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER;RELATED=START:-PT5M +DESCRIPTION:Reminder +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/samples/timezone_from_file.ics b/samples/timezone_from_file.ics new file mode 100644 index 0000000..0449ab0 --- /dev/null +++ b/samples/timezone_from_file.ics @@ -0,0 +1,18 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:Nowhere/Middle +BEGIN:STANDARD +DTSTART:16010101T000000 +TZOFFSETFROM:-0741 +TZOFFSETTO:-0741 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTAMP:20230306T080000Z +DTSTART;TZID=Nowhere/Middle:20230306T134200 +DTEND;TZID=Nowhere/Middle:20230306T144200 +SUMMARY:A test event +END:VEVENT +END:VCALENDAR diff --git a/samples/timezones/America/Atikokan.ics b/samples/timezones/America/Atikokan.ics new file mode 100644 index 0000000..2fe0eb9 --- /dev/null +++ b/samples/timezones/America/Atikokan.ics @@ -0,0 +1,14 @@ +BEGIN:VCALENDAR +PRODID:-//tzurl.org//NONSGML Olson 2012h//EN +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:America/Atikokan +X-LIC-LOCATION:America/Atikokan +BEGIN:STANDARD +TZOFFSETFROM:-0500 +TZOFFSETTO:-0500 +TZNAME:EST +DTSTART:19700101T000000 +END:STANDARD +END:VTIMEZONE +END:VCALENDAR diff --git a/samples/timezones/America/Denver.ics b/samples/timezones/America/Denver.ics new file mode 100644 index 0000000..93636f2 --- /dev/null +++ b/samples/timezones/America/Denver.ics @@ -0,0 +1,41 @@ +BEGIN:VCALENDAR +PRODID:-//custom/thing +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:America/Denver +BEGIN:DAYLIGHT +TZOFFSETFROM:-0700 +TZOFFSETTO:-0600 +TZNAME:MDT +DTSTART:20070311T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0600 +TZOFFSETTO:-0700 +TZNAME:MST +DTSTART:20071104T020000 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETFROM:-0700 +TZOFFSETTO:-0600 +TZNAME:MDT +DTSTART:19180331T020000 +RDATE:20030406T020000 +RDATE:20040404T020000 +RDATE:20050403T020000 +RDATE:20060402T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0600 +TZOFFSETTO:-0700 +TZNAME:MST +DTSTART:19181027T020000 +RDATE:20031026T020000 +RDATE:20041031T020000 +RDATE:20051030T020000 +RDATE:20061029T020000 +END:STANDARD +END:VTIMEZONE +END:VCALENDAR diff --git a/samples/timezones/America/Los_Angeles.ics b/samples/timezones/America/Los_Angeles.ics new file mode 100644 index 0000000..09d1858 --- /dev/null +++ b/samples/timezones/America/Los_Angeles.ics @@ -0,0 +1,22 @@ +BEGIN:VCALENDAR +PRODID:-//tzurl.org//NONSGML Olson 2012h//EN +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:America/Los_Angeles +X-LIC-LOCATION:America/Los_Angeles +BEGIN:DAYLIGHT +TZOFFSETFROM:-0800 +TZOFFSETTO:-0700 +TZNAME:PDT +DTSTART:19700308T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0700 +TZOFFSETTO:-0800 +TZNAME:PST +DTSTART:19701101T020000 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +END:STANDARD +END:VTIMEZONE +END:VCALENDAR diff --git a/samples/timezones/America/New_York.ics b/samples/timezones/America/New_York.ics new file mode 100644 index 0000000..cf83f9a --- /dev/null +++ b/samples/timezones/America/New_York.ics @@ -0,0 +1,22 @@ +BEGIN:VCALENDAR +PRODID:-//tzurl.org//NONSGML Olson 2012h//EN +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:America/New_York +X-LIC-LOCATION:America/New_York +BEGIN:DAYLIGHT +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +TZNAME:EDT +DTSTART:19700308T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +TZNAME:EST +DTSTART:19701101T020000 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +END:STANDARD +END:VTIMEZONE +END:VCALENDAR diff --git a/samples/timezones/Makebelieve/RDATE_test.ics b/samples/timezones/Makebelieve/RDATE_test.ics new file mode 100644 index 0000000..8360147 --- /dev/null +++ b/samples/timezones/Makebelieve/RDATE_test.ics @@ -0,0 +1,26 @@ +BEGIN:VCALENDAR +PRODID:-//ical.js//NONSGML Makebelieve//EN +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:Makebelieve/RDATE_as_date +X-LIC-LOCATION:Makebelieve/RDATE_as_date +BEGIN:STANDARD +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +TZNAME:RDATE_as_date_standard +DTSTART:19700101T020000 +RDATE:19700101T020000 +RDATE;VALUE=DATE:19800101 +RDATE:19900101T070000Z +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +TZNAME:RDATE_as_date_daylight +DTSTART:19750101T020000 +RDATE:19750101T020000 +RDATE;VALUE=DATE:19850101 +RDATE:19950101T070000Z +END:DAYLIGHT +END:VTIMEZONE +END:VCALENDAR diff --git a/samples/timezones/Makebelieve/RDATE_utc_test.ics b/samples/timezones/Makebelieve/RDATE_utc_test.ics new file mode 100644 index 0000000..331cf25 --- /dev/null +++ b/samples/timezones/Makebelieve/RDATE_utc_test.ics @@ -0,0 +1,26 @@ +BEGIN:VCALENDAR +PRODID:-//ical.js//NONSGML Makebelieve//EN +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:Makebelieve/RDATE_as_date_utc +X-LIC-LOCATION:Makebelieve/RDATE_as_date_utc +BEGIN:STANDARD +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +TZNAME:RDATE_as_date_utc_standard +DTSTART:19700101T020000Z +RDATE:19700101T020000 +RDATE;VALUE=DATE:19800101 +RDATE:19900101T070000Z +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +TZNAME:RDATE_as_date_utc_daylight +DTSTART:19750101T020000Z +RDATE:19750101T020000 +RDATE;VALUE=DATE:19850101 +RDATE:19950101T070000Z +END:DAYLIGHT +END:VTIMEZONE +END:VCALENDAR diff --git a/samples/timezones/Makebelieve/RRULE_UNTIL_test.ics b/samples/timezones/Makebelieve/RRULE_UNTIL_test.ics new file mode 100644 index 0000000..07d6c05 --- /dev/null +++ b/samples/timezones/Makebelieve/RRULE_UNTIL_test.ics @@ -0,0 +1,22 @@ +BEGIN:VCALENDAR +PRODID:-//ical.js//NONSGML Makebelieve//EN +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:Makebelieve/RRULE_UNTIL +X-LIC-LOCATION:Makebelieve/RRULE_UNTIL +BEGIN:STANDARD +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +TZNAME:RRULE_UNTIL_standard +DTSTART:19700101T020000Z +RRULE:FREQ=YEARLY;INTERVAL=5;UNTIL=19800101T020000Z +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +TZNAME:RDATE_UNTIL_daylight +DTSTART:19750101T020000 +RRULE:FREQ=YEARLY;INTERVAL=5;UNTIL=19850101T020000Z +END:DAYLIGHT +END:VTIMEZONE +END:VCALENDAR diff --git a/samples/utc_negative_zero.ics b/samples/utc_negative_zero.ics new file mode 100644 index 0000000..e61ad9a --- /dev/null +++ b/samples/utc_negative_zero.ics @@ -0,0 +1,27 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:Zimbra-Calendar-Provider +BEGIN:VTIMEZONE +TZID:Etc/GMT +BEGIN:STANDARD +DTSTART:19710101T000000 +TZOFFSETTO:-0000 +TZOFFSETFROM:-0000 +TZNAME:GMT +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +UID:d118e997-3683-4552-8fe8-57c641f1f179 +SUMMARY:And another +ORGANIZER;CN=Sahaja Lal:mailto:calmozilla1@yahoo.com +DTSTART;TZID=Etc/GMT:20120821T210000 +DTEND;TZID=Etc/GMT:20120821T213000 +STATUS:CONFIRMED +CLASS:PUBLIC +X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY +TRANSP:OPAQUE +X-MICROSOFT-DISALLOW-COUNTER:TRUE +DTSTAMP:20120817T032509Z +SEQUENCE:0 +END:VEVENT +END:VCALENDAR diff --git a/test/acceptance/blank_description_test.js b/test/acceptance/blank_description_test.js new file mode 100644 index 0000000..5a23421 --- /dev/null +++ b/test/acceptance/blank_description_test.js @@ -0,0 +1,12 @@ +suite('ics - blank description', function() { + let icsData; + + suiteSetup(async function() { + icsData = await testSupport.loadSample('blank_description.ics'); + }); + + test('summary', function() { + // just verify it can parse blank lines + ICAL.parse(icsData); + }); +}); diff --git a/test/acceptance/daily_recuring_test.js b/test/acceptance/daily_recuring_test.js new file mode 100644 index 0000000..31ccc8e --- /dev/null +++ b/test/acceptance/daily_recuring_test.js @@ -0,0 +1,31 @@ +suite('ics - blank description', function() { + let icsData; + + suiteSetup(async function() { + icsData = await testSupport.loadSample('daily_recur.ics'); + }); + + test('summary', function() { + // just verify it can parse blank lines + let result = ICAL.parse(icsData); + let component = new ICAL.Component(result); + let vevent = component.getFirstSubcomponent( + 'vevent' + ); + + let recur = vevent.getFirstPropertyValue( + 'rrule' + ); + + let start = vevent.getFirstPropertyValue( + 'dtstart' + ); + + let iter = recur.iterator(start); + let limit = 10; + while (limit) { + iter.next(); + limit--; + } + }); +}); diff --git a/test/acceptance/forced_types_test.js b/test/acceptance/forced_types_test.js new file mode 100644 index 0000000..0aa84b0 --- /dev/null +++ b/test/acceptance/forced_types_test.js @@ -0,0 +1,20 @@ +suite('ics test', function() { + let icsData; + + suiteSetup(async function() { + icsData = await testSupport.loadSample('forced_types.ics'); + }); + + test('force type', function() { + // just verify it can parse forced types + let result = ICAL.parse(icsData); + let component = new ICAL.Component(result); + let vevent = component.getFirstSubcomponent( + 'vevent' + ); + + let start = vevent.getFirstPropertyValue('dtstart'); + + assert.isTrue(start.isDate, 'is date type'); + }); +}); diff --git a/test/acceptance/google_birthday_test.js b/test/acceptance/google_birthday_test.js new file mode 100644 index 0000000..3e02b93 --- /dev/null +++ b/test/acceptance/google_birthday_test.js @@ -0,0 +1,50 @@ +suite('google birthday events', function() { + let icsData; + + suiteSetup(async function() { + icsData = await testSupport.loadSample('google_birthday.ics'); + }); + + test('expanding malformatted recurring event', function(done) { + // just verify it can parse forced types + let parser = new ICAL.ComponentParser(); + let primary; + let exceptions = []; + + let expectedDates = [ + new Date(2012, 11, 10), + new Date(2013, 11, 10), + new Date(2014, 11, 10) + ]; + + parser.onevent = function(event) { + if (event.isRecurrenceException()) { + exceptions.push(event); + } else { + primary = event; + } + }; + + parser.oncomplete = function() { + exceptions.forEach(function(item) { + primary.relateException(item); + }); + + let iter = primary.iterator(); + let next; + let dates = []; + while ((next = iter.next())) { + dates.push(next.toJSDate()); + } + + assert.deepEqual( + dates, + expectedDates + ); + + done(); + }; + + parser.process(icsData); + }); +}); diff --git a/test/acceptance/utc_negative_zero_test.js b/test/acceptance/utc_negative_zero_test.js new file mode 100644 index 0000000..667bb55 --- /dev/null +++ b/test/acceptance/utc_negative_zero_test.js @@ -0,0 +1,28 @@ +suite('ics - negative zero', function() { + let icsData; + + suiteSetup(async function() { + icsData = await testSupport.loadSample('utc_negative_zero.ics'); + }); + + test('summary', function() { + let result = ICAL.parse(icsData); + let component = new ICAL.Component(result); + let vtimezone = component.getFirstSubcomponent( + 'vtimezone' + ); + + let standard = vtimezone.getFirstSubcomponent( + 'standard' + ); + + let props = standard.getAllProperties(); + let offset = props[1].getFirstValue(); + + assert.equal( + offset.factor, + -1, + 'offset' + ); + }); +}); diff --git a/test/binary_test.js b/test/binary_test.js new file mode 100644 index 0000000..cc68eab --- /dev/null +++ b/test/binary_test.js @@ -0,0 +1,23 @@ +suite('ICAL.Binary', function() { + let subject; + + setup(function() { + subject = new ICAL.Binary(); + }); + + test('setEncodedValue', function() { + subject.setEncodedValue('bananas'); + assert.equal(subject.decodeValue(), 'bananas'); + assert.equal(subject.value, 'YmFuYW5hcw=='); + + subject.setEncodedValue('apples'); + assert.equal(subject.decodeValue(), 'apples'); + assert.equal(subject.value, 'YXBwbGVz'); + }); + + test('null values', function() { + subject.setEncodedValue(null); + assert.equal(subject.decodeValue(), null); + assert.equal(subject.value, null); + }); +}); diff --git a/test/component_parser_test.js b/test/component_parser_test.js new file mode 100644 index 0000000..c0f6eb0 --- /dev/null +++ b/test/component_parser_test.js @@ -0,0 +1,124 @@ +suite('component_parser', function() { + let subject; + let icsData; + + suiteSetup(async function() { + icsData = await testSupport.loadSample('recur_instances.ics'); + }); + + suite('#process', function() { + let events = []; + let exceptions = []; + let timezones = []; + + function eventEquals(a, b, msg) { + if (!a) + throw new Error('actual is falsy'); + + if (!b) + throw new Error('expected is falsy'); + + if (a instanceof ICAL.Event) { + a = a.component; + } + + if (b instanceof ICAL.Event) { + b = b.component; + } + + assert.deepEqual(a.toJSON(), b.toJSON(), msg); + } + + function setupProcess(options) { + setup(function(done) { + events.length = 0; + timezones.length = 0; + + subject = new ICAL.ComponentParser(options); + + subject.onrecurrenceexception = function(item) { + exceptions.push(item); + }; + + subject.onevent = function(event) { + events.push(event); + }; + + subject.ontimezone = function(tz) { + timezones.push(tz); + }; + + subject.oncomplete = function() { + done(); + }; + + subject.process(ICAL.parse(icsData)); + }); + } + + suite('without events', function() { + setupProcess({ parseEvent: false }); + + test('parse result', function() { + assert.lengthOf(events, 0); + assert.lengthOf(timezones, 1); + + let tz = timezones[0]; + assert.instanceOf(tz, ICAL.Timezone); + assert.equal(tz.tzid, 'America/Los_Angeles'); + }); + + }); + + suite('with events', function() { + setupProcess(); + + test('parse result', function() { + let component = new ICAL.Component(ICAL.parse(icsData)); + let list = component.getAllSubcomponents('vevent'); + + let expectedEvents = []; + + list.forEach(function(item) { + expectedEvents.push(new ICAL.Event(item)); + }); + + assert.instanceOf(expectedEvents[0], ICAL.Event); + + eventEquals(events[0], expectedEvents[0]); + eventEquals(events[1], expectedEvents[1]); + eventEquals(events[2], expectedEvents[2]); + }); + }); + + suite('without parsing timezones', function() { + setupProcess({ parseTimezone: false }); + + test('parse result', function() { + assert.lengthOf(timezones, 0); + assert.lengthOf(events, 3); + }); + }); + + suite('alternate input', function() { + test('parsing component from string', function(done) { + subject = new ICAL.ComponentParser(); + subject.oncomplete = function() { + assert.lengthOf(events, 3); + done(); + }; + subject.process(icsData); + }); + test('parsing component from component', function(done) { + subject = new ICAL.ComponentParser(); + subject.oncomplete = function() { + assert.lengthOf(events, 3); + done(); + }; + let comp = new ICAL.Component(ICAL.parse(icsData)); + subject.process(comp); + }); + }); + }); + +}); diff --git a/test/component_test.js b/test/component_test.js new file mode 100644 index 0000000..16a4b58 --- /dev/null +++ b/test/component_test.js @@ -0,0 +1,627 @@ +suite('Component', function() { + let subject; + let fixtures; + + setup(function() { + fixtures = { + components: [ + 'vevent', + [ + ['description', {}, 'text', 'xfoo'], + ['description', {}, 'text', 'xfoo2'], + ['xfoo', {}, 'text', 'xfoo3'] + ], + [ + ['valarm', [], []], + ['vtodo', [], []], + ['valarm', [['description', {}, 'text', 'foo']], []] + ] + ] + }; + + subject = new ICAL.Component(fixtures.components); + }); + + suite("initialization", function() { + test('initialize component', function() { + let raw = ['description', {}, 'text', 'value']; + subject = new ICAL.Component(raw); + + assert.equal(subject.jCal, raw, 'has jCal'); + assert.equal(subject.name, 'description'); + }); + + test('new component without jCal', function() { + let newComp = new ICAL.Component('vevent'); + + assert.equal(newComp.jCal[0], 'vevent'); + + assert.lengthOf(newComp.getAllSubcomponents(), 0); + assert.lengthOf(newComp.getAllProperties(), 0); + }); + + test("#fromString", function() { + let comp = ICAL.Component.fromString("BEGIN:VCALENDAR\nX-CALPROP:value\nEND:VCALENDAR"); + assert.equal(comp.name, "vcalendar"); + let prop = comp.getFirstProperty(); + assert.equal(prop.name, "x-calprop"); + assert.equal(prop.getFirstValue(), "value"); + }); + }); + + suite('parenting', function() { + // Today we hear a tale about Tom, Marge, Bernhard and Claire. + let tom, bernhard, claire, marge, relationship; + let house, otherhouse; + setup(function() { + tom = new ICAL.Component("tom"); + bernhard = new ICAL.Component("bernhard"); + claire = new ICAL.Component("claire"); + marge = new ICAL.Component("marge"); + relationship = new ICAL.Component("vrelationship"); + house = new ICAL.Property("house"); + otherhouse = new ICAL.Property("otherhouse"); + }); + + test('basic', function() { + // Tom and Bernhard are best friends. They are happy and single. + assert.isNull(tom.parent); + assert.isNull(bernhard.parent); + + // One day, they get to know Marge, who is also single. + assert.isNull(marge.parent); + + // Tom and Bernhard play rock paper scissors on who gets a first shot at + // Marge and Tom wins. After a few nice dates they get together. + relationship.addSubcomponent(tom); + relationship.addSubcomponent(marge); + + // Both are happy as can be and tell everyone about their love. Nothing + // goes above their relationship! + assert.isNull(relationship.parent); + assert.equal(tom.parent, relationship); + assert.equal(marge.parent, relationship); + + // Over the years, there are a few ups and downs. + relationship.removeSubcomponent(tom); + assert.isNull(relationship.parent); + assert.isNull(tom.parent); + assert.equal(marge.parent, relationship); + relationship.removeAllSubcomponents(); + assert.isNull(marge.parent); + + // But in the end they stay together. + relationship.addSubcomponent(tom); + relationship.addSubcomponent(marge); + }); + + test('multiple children', function() { + // After some happy years Tom and Marge get married. Tom is going to be father + // of his beautiful daughter Claire. + tom.addSubcomponent(claire); + + // He has no doubt he is the father + assert.equal(claire.parent, tom); + + // One day, Tom catches his wife in bed with his best friend Bernhard. + // Tom is very unhappy and requests a paternity test. It turns out that + // Claire is actually Bernhard's daughter. + bernhard.addSubcomponent(claire); + + // Bernhard is happy to hear about his daughter, while Tom goes about to + // tell everyone he knows. Claire is devastated and would have rather + // found out about this. + assert.isFalse(tom.removeSubcomponent(claire)); + + // Marge knew it all along. What a sad day. Claire is not Tom's daughter, + // but instead Bernhard's. Tom has no children, and Bernhard is the happy + // father of his daughter claire. + assert.equal(claire.parent, bernhard); + assert.isNull(tom.getFirstSubcomponent()); + assert.equal(bernhard.getFirstSubcomponent(), claire); + + // Feeling depressed, Tom tries to find happyness with a pet, but all he + // got was scratches and sadness. That didn't go so well. + assert.throws(function() { + tom.addProperty("bird"); + }, 'must be instance of ICAL.Property'); + }); + + test('properties', function() { + // Marge lives on a property near the Hamptons, she thinks it belongs to + // her. + marge.addProperty(house); + assert.equal(house.parent, marge); + + // It seems that Tom didn't always trust Marge, he had fooled her. The + // house belongs to him. + tom.addProperty(house); + assert.equal(house.parent, tom); + assert.isNull(marge.getFirstProperty()); + + // Bernhard being an aggressive character, tries to throw Tom out of his + // own house. A long visit in the hospital lets neighbors believe noone + // lives there anymore. + tom.removeProperty(house); + assert.isNull(house.parent); + + // Marge spends a few nights there, but also lives in her other house. + marge.addProperty(house); + marge.addProperty(otherhouse); + assert.equal(house.parent, marge); + assert.equal(otherhouse.parent, marge); + + // Tom is back from the hospital and very mad. He throws marge out of his + // house. Unfortunately marge can no longer pay the rent for her other + // house either. + marge.removeAllProperties(); + assert.isNull(house.parent); + assert.isNull(otherhouse.parent); + + // What a mess. What do we learn from this testsuite? Infidelity is not a + // good idea. Always be faithful! + }); + }); + + suite('#getFirstSubcomponent', function() { + let jCal; + setup(function() { + jCal = fixtures.components; + subject = new ICAL.Component(jCal); + }); + + test('without name', function() { + let component = subject.getFirstSubcomponent(); + assert.equal(component.parent, subject); + assert.equal(component.name, 'valarm'); + + // first sub component + let expected = jCal[2][0]; + + assert.equal(component.jCal, expected); + }); + + test('with name (when not first)', function() { + let component = subject.getFirstSubcomponent( + 'vtodo' + ); + + assert.equal(component.parent, subject); + + assert.equal(component.name, 'vtodo'); + assert.equal( + component.jCal, + jCal[2][1] + ); + }); + + test('with name (when there are two)', function() { + let component = subject.getFirstSubcomponent( + 'valarm' + ); + assert.equal(component.name, 'valarm'); + assert.equal( + component.jCal, + jCal[2][0] + ); + }); + + test('equality between calls', function() { + assert.equal( + subject.getFirstSubcomponent(), + subject.getFirstSubcomponent() + ); + }); + }); + + suite('#getAllSubcomponents', function() { + test('with components', function() { + // 2 is the component array + let comps = fixtures.components[2]; + + subject = new ICAL.Component( + fixtures.components + ); + + let result = subject.getAllSubcomponents(); + assert.lengthOf(result, comps.length); + + for (let i = 0; i < comps.length; i++) { + assert.instanceOf(result[i], ICAL.Component); + assert.equal(result[i].jCal, comps[i]); + } + }); + + test('with name', function() { + subject = new ICAL.Component(fixtures.components); + + let result = subject.getAllSubcomponents('valarm'); + assert.lengthOf(result, 2); + + result.forEach(function(item) { + assert.equal(item.name, 'valarm'); + }); + }); + + test('without components', function() { + subject = new ICAL.Component(['foo', [], []]); + assert.equal(subject.name, 'foo'); + assert.lengthOf(subject.getAllSubcomponents(), 0); + }); + + test('with name from end', function() { + // We need our own subject for this test + let oursubject = new ICAL.Component(fixtures.components); + + // Get one from the end first + let comps = fixtures.components[2]; + oursubject.getAllSubcomponents(comps[comps.length - 1][0]); + + // Now get them all, they MUST be hydrated + let results = oursubject.getAllSubcomponents(); + for (let i = 0; i < results.length; i++) { + assert.isDefined(results[i]); + assert.equal(results[i].jCal, subject.jCal[2][i]); + } + }); + }); + + test('#addSubcomponent', function() { + let newComp = new ICAL.Component('xnew'); + + subject.addSubcomponent(newComp); + let all = subject.getAllSubcomponents(); + + assert.equal( + all[all.length - 1], + newComp, + 'can reference component' + ); + + assert.equal( + all.length, + subject.jCal[2].length, + 'has same number of items' + ); + + assert.equal( + subject.jCal[2][all.length - 1], + newComp.jCal, + 'adds jCal' + ); + }); + + suite('#removeSubcomponent', function() { + test('by name', function() { + subject.removeSubcomponent('vtodo'); + + let all = subject.getAllSubcomponents(); + + all.forEach(function(item) { + assert.equal(item.name, 'valarm'); + }); + }); + + test('by component', function() { + let first = subject.getFirstSubcomponent(); + + subject.removeSubcomponent(first); + + assert.notEqual( + subject.getFirstSubcomponent(), + first + ); + + assert.equal( + subject.getFirstSubcomponent().name, + 'vtodo' + ); + }); + + test('remove non hydrated subcomponent should not shift hydrated property', function() { + let component = new ICAL.Component([ + 'vevent', + [], + [ + ['a', [], []], + ['b', [], []], + ['c', [], []] + ] + ]); + component.getFirstSubcomponent('b'); + component.removeSubcomponent('a'); + let cValue = component.getFirstSubcomponent('c').name; + assert.equal(cValue, 'c'); + }); + }); + + suite('#removeAllSubcomponents', function() { + test('with name', function() { + subject.removeAllSubcomponents('valarm'); + assert.lengthOf(subject.jCal[2], 1); + assert.equal(subject.jCal[2][0][0], 'vtodo'); + assert.lengthOf(subject.getAllSubcomponents(), 1); + }); + + test('all', function() { + subject.removeAllSubcomponents(); + assert.lengthOf(subject.jCal[2], 0); + assert.lengthOf(subject.getAllSubcomponents(), 0); + }); + }); + + test('#hasProperty', function() { + subject = new ICAL.Component( + fixtures.components + ); + + assert.ok(subject.hasProperty('description')); + assert.ok(!subject.hasProperty('iknowitsnothere')); + }); + + suite('#getFirstProperty', function() { + setup(function() { + subject = new ICAL.Component(fixtures.components); + }); + + test('name missing', function() { + assert.ok(!subject.getFirstProperty('x-foo')); + }); + + test('name has multiple', function() { + let first = subject.getFirstProperty('description'); + assert.equal(first, subject.getFirstProperty()); + + assert.equal( + first.getFirstValue(), + 'xfoo' + ); + }); + + test('without name', function() { + let first = subject.getFirstProperty(); + assert.equal( + first.jCal, + fixtures.components[1][0] + ); + }); + + test('without name empty', function() { + subject = new ICAL.Component(['foo', [], []]); + assert.ok(!subject.getFirstProperty()); + }); + }); + + test('#getFirstPropertyValue', function() { + subject = new ICAL.Component(fixtures.components); + assert.equal( + subject.getFirstPropertyValue(), + 'xfoo' + ); + }); + + suite('#getAllProperties', function() { + setup(function() { + subject = new ICAL.Component(fixtures.components); + }); + + test('with name', function() { + let results = subject.getAllProperties('description'); + assert.lengthOf(results, 2); + + results.forEach(function(item, i) { + assert.equal( + item.jCal, + subject.jCal[1][i] + ); + }); + }); + + test('with name empty', function() { + let results = subject.getAllProperties('wtfmissing'); + assert.deepEqual(results, []); + }); + + test('without name', function() { + let results = subject.getAllProperties(); + results.forEach(function(item, i) { + assert.equal(item.jCal, subject.jCal[1][i]); + }); + }); + + test('with name from end', function() { + // We need our own subject for this test + let oursubject = new ICAL.Component(fixtures.components); + + // Get one from the end first + let props = fixtures.components[1]; + oursubject.getAllProperties(props[props.length - 1][0]); + + // Now get them all, they MUST be hydrated + let results = oursubject.getAllProperties(); + for (let i = 0; i < results.length; i++) { + assert.isDefined(results[i]); + assert.equal(results[i].jCal, subject.jCal[1][i]); + } + }); + }); + + test('#addProperty', function() { + let prop = new ICAL.Property('description'); + + subject.addProperty(prop); + assert.equal(subject.jCal[1][3], prop.jCal); + + let all = subject.getAllProperties(); + let lastProp = all[all.length - 1]; + + assert.equal(lastProp, prop); + assert.equal(lastProp.parent, subject); + }); + + test('#addPropertyWithValue', function() { + subject = new ICAL.Component('vevent'); + + subject.addPropertyWithValue('description', 'value'); + + let all = subject.getAllProperties(); + + assert.equal(all[0].name, 'description'); + assert.equal(all[0].getFirstValue(), 'value'); + }); + + test('#updatePropertyWithValue', function() { + subject = new ICAL.Component('vevent'); + subject.addPropertyWithValue('description', 'foo'); + assert.lengthOf(subject.getAllProperties(), 1); + + subject.updatePropertyWithValue('description', 'xxx'); + + assert.equal(subject.getFirstPropertyValue('description'), 'xxx'); + subject.updatePropertyWithValue('x-foo', 'bar'); + + let list = subject.getAllProperties(); + assert.sameDeepMembers(list.map(prop => [prop.name, prop.getValues()]), [["x-foo", ["bar"]], ["description", ["xxx"]]]); + assert.equal(subject.getFirstPropertyValue('x-foo'), 'bar'); + }); + + suite('#removeProperty', function() { + setup(function() { + subject = new ICAL.Component( + fixtures.components + ); + }); + + test('try to remove non-existent', function() { + let result = subject.removeProperty('wtfbbq'); + assert.isFalse(result); + }); + + test('remove by property', function() { + let first = subject.getFirstProperty('description'); + + let result = subject.removeProperty(first); + assert.isTrue(result, 'removes property'); + + assert.notEqual( + subject.getFirstProperty('description'), + first + ); + + assert.lengthOf(subject.jCal[1], 2); + }); + + test('remove by name', function() { + // there are two descriptions + let list = subject.getAllProperties(); + let first = subject.getFirstProperty('description'); + + let result = subject.removeProperty('description'); + assert.isTrue(result); + + assert.notEqual( + subject.getFirstProperty('description'), + first + ); + + assert.lengthOf(list, 2); + }); + + test('remove non hydrated property should not shift hydrated property', function() { + let component = new ICAL.Component([ + 'vevent', + [ + ['a', {}, 'text', 'a'], + ['b', {}, 'text', 'b'], + ['c', {}, 'text', 'c'] + ], + ]); + component.getFirstPropertyValue('b'); + component.removeProperty('a'); + let cValue = component.getFirstPropertyValue('c'); + assert.equal(cValue, 'c'); + }); + }); + + suite('#removeAllProperties', function() { + test('no name when empty', function() { + subject = new ICAL.Component( + fixtures.components + ); + + assert.lengthOf(subject.jCal[1], 3); + + subject.removeAllProperties(); + + assert.lengthOf(subject.jCal[1], 0); + assert.ok(!subject.getFirstProperty()); + }); + + test('no name when not empty', function() { + subject = new ICAL.Component(['vevent', [], []]); + subject.removeAllProperties(); + subject.removeAllProperties('xfoo'); + }); + + test('with name', function() { + subject = new ICAL.Component( + fixtures.components + ); + + subject.removeAllProperties('description'); + assert.lengthOf(subject.jCal[1], 1); + + let first = subject.getFirstProperty(); + + assert.equal(first.name, 'xfoo'); + assert.equal(subject.jCal[1][0][0], 'xfoo'); + }); + }); + + test('#toJSON', function() { + let json = JSON.stringify(subject); + let fromJSON = new ICAL.Component(JSON.parse(json)); + + assert.deepEqual( + fromJSON.jCal, + subject.jCal + ); + }); + + test('#toString', function() { + let ical = subject.toString(); + let parsed = ICAL.parse(ical); + let fromICAL = new ICAL.Component(parsed); + + assert.deepEqual(subject.jCal, fromICAL.jCal); + }); + + test('#getTimeZoneByID', async function() { + let icsData = await testSupport.loadSample('timezone_from_file.ics'); + let vcalendar = new ICAL.Component(ICAL.parse(icsData)); + + let zone = vcalendar.getTimeZoneByID("Nowhere/Middle"); + assert.equal(zone.tzid, "Nowhere/Middle"); + + // Zone remains in cache + vcalendar.removeSubcomponent("vtimezone"); + zone = vcalendar.getTimeZoneByID("Nowhere/Middle"); + assert.equal(zone.tzid, "Nowhere/Middle"); + + // Lookup from child component + zone = vcalendar.getFirstSubcomponent("vevent").getTimeZoneByID("Nowhere/Middle"); + assert.equal(zone.tzid, "Nowhere/Middle"); + + // Non vcalendar root component + let vother = new ICAL.Component(["x-other", [], [["vtimezone", [], []]]]); + zone = vother.getFirstSubcomponent().getTimeZoneByID("Nowhere/Middle"); + assert.isNull(zone); + + + // Missing timezone definition + vcalendar = new ICAL.Component(ICAL.parse(icsData)); + vcalendar.removeSubcomponent("vtimezone"); + zone = vcalendar.getTimeZoneByID("Nowhere/Middle"); + assert.isNull(zone); + }); +}); diff --git a/test/design_test.js b/test/design_test.js new file mode 100644 index 0000000..dbd411a --- /dev/null +++ b/test/design_test.js @@ -0,0 +1,967 @@ +suite('design', function() { + + let timezone; + suiteSetup(async function() { + let data = await testSupport.loadSample('timezones/America/New_York.ics'); + let parsed = ICAL.parse(data); + let vcalendar = new ICAL.Component(parsed); + let vtimezone = vcalendar.getFirstSubcomponent('vtimezone'); + + timezone = new ICAL.Timezone(vtimezone); + ICAL.TimezoneService.register('test', timezone); + }); + + suiteTeardown(function() { + ICAL.TimezoneService.reset(); + }); + + let subject; + setup(function() { + subject = ICAL.design.defaultSet; + }); + + suite('types', function() { + + suite('binary', function() { + setup(function() { + subject = subject.value.binary; + }); + + test('#(un)decorate', function() { + let expectedDecode = 'The quick brown fox jumps over the lazy dog.'; + let undecorated = 'VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcy' + + 'BvdmVyIHRoZSBsYXp5IGRvZy4='; + + let decorated = subject.decorate(undecorated); + let decoded = decorated.decodeValue(); + + assert.equal(decoded, expectedDecode); + + assert.equal( + subject.undecorate(decorated), + undecorated + ); + }); + }); + + suite('date', function() { + setup(function() { + subject = subject.value.date; + }); + + test('#fromICAL', function() { + let value = subject.fromICAL( + '20121010' + ); + + assert.equal(value, '2012-10-10'); + }); + + test('#toICAL', function() { + let value = subject.toICAL( + '2012-10-10' + ); + + assert.equal(value, '20121010'); + }); + + test('#to/fromICAL (lenient)', function() { + let value = '20120901T130000'; + let expected = '2012-09-01T13:00:00'; + + ICAL.design.strict = false; + assert.equal( + subject.fromICAL(value), + expected + ); + + assert.equal( + subject.toICAL(expected), + value + ); + ICAL.design.strict = true; + }); + + test('#toICAL invalid', function() { + let value = subject.toICAL( + 'wheeeeeeeeeeeeee' + ); + + assert.equal(value, 'wheeeeeeeeeeeeee'); + }); + + + test('#fromICAL somewhat invalid', function() { + // Strict mode is not completely strict, it takes a lot of shortcuts in the name of + // performance. The functions in ICAL.design don't actually throw errors, given there is no + // error collector. With a working error collector we should make lenient mode the default + // and have strict mode be more pedantic. + let value = subject.fromICAL('20131210Z'); + assert.equal(value, '2013-12-10'); + }); + + test('#(un)decorate (lenient)', function() { + let value = '2012-10-10T11:12:13'; + let prop = new ICAL.Property(['date', { tzid: 'test' }]); + + ICAL.design.strict = false; + + let time = subject.decorate( + value, + prop + ); + + assert.hasProperties( + time, + { year: 2012, month: 10, day: 10, hour: 11, minute: 12, second: 13, isDate: false } + ); + + assert.equal( + subject.undecorate(time), + value + ); + ICAL.design.strict = true; + + }); + + test('#(un)decorate (custom timezone)', function() { + let value = '2012-10-10'; + let prop = new ICAL.Property(['date', { tzid: 'test' }]); + + let time = subject.decorate( + value, + prop + ); + + assert.hasProperties( + time, + { year: 2012, month: 10, day: 10, isDate: true } + ); + + assert.equal( + subject.undecorate(time), + value + ); + }); + }); + + suite('date-time', function() { + setup(function() { + subject = subject.value['date-time']; + }); + + test('#(from|to)ICAL', function() { + let value = '20120901T130000'; + let expected = '2012-09-01T13:00:00'; + + assert.equal( + subject.fromICAL(value), + expected + ); + + assert.equal( + subject.toICAL(expected), + value + ); + }); + test('#toICAL invalid', function() { + let value = subject.toICAL( + 'wheeeeeeeeeeeeee' + ); + + assert.equal(value, 'wheeeeeeeeeeeeee'); + }); + + test('#from/toICAL (lenient)', function() { + let value = '20190102'; + let expected = '2019-01-02'; + + ICAL.design.strict = false; + assert.equal( + subject.fromICAL(value), + expected + ); + + assert.equal( + subject.toICAL(expected), + value + ); + ICAL.design.strict = true; + }); + test('#(un)decorate (lenient)', function() { + ICAL.design.strict = false; + let undecorated = '2012-09-01'; + let prop = new ICAL.Property(['date-time', {}]); + + let decorated = subject.decorate(undecorated, prop); + + assert.hasProperties( + decorated, + { + year: 2012, + month: 9, + day: 1, + isDate: true + } + ); + + assert.equal( + subject.undecorate(decorated), + undecorated + ); + ICAL.design.strict = true; + }); + + test('#(un)decorate (utc)', function() { + let undecorated = '2012-09-01T13:05:11Z'; + let prop = new ICAL.Property(['date-time', {}]); + + let decorated = subject.decorate(undecorated, prop); + + assert.hasProperties( + decorated, + { + year: 2012, + month: 9, + day: 1, + hour: 13, + minute: 5, + second: 11, + isDate: false, + zone: ICAL.Timezone.utcTimezone + } + ); + + assert.equal( + subject.undecorate(decorated), + undecorated + ); + }); + + test('#(un)decorate (custom timezone)', function() { + let prop = new ICAL.Property( + ['date-time', { tzid: 'test' }] + ); + assert.equal(prop.getParameter('tzid'), 'test'); + + ICAL.TimezoneService.register( + 'America/Los_Angeles', + ICAL.Timezone.utcTimezone + ); + + let undecorated = '2012-09-01T13:05:11'; + let decorated = subject.decorate(undecorated, prop); + assert.equal(decorated.zone, timezone); + + assert.hasProperties( + decorated, + { + year: 2012, + month: 9, + day: 1, + hour: 13, + minute: 5, + second: 11, + isDate: false + } + ); + + assert.equal( + subject.undecorate(decorated), + undecorated + ); + }); + }); + + suite('time', function() { + setup(function() { + subject = subject.value.time; + }); + + test('#fromICAL', function() { + let value = subject.fromICAL( + '232050' + ); + + assert.equal(value, '23:20:50'); + }); + test('#fromICAL invalid', function() { + let value = subject.fromICAL( + 'whoop' + ); + + assert.equal(value, 'whoop'); + }); + + test('#toICAL', function() { + let value = subject.toICAL( + '23:20:50' + ); + + assert.equal(value, '232050'); + }); + test('#toICAL invalid', function() { + let value = subject.toICAL( + 'whoop' + ); + + assert.equal(value, 'whoop'); + }); + }); + + suite('vcard date/time types', function() { + function testRoundtrip(jcal, ical, props, only) { + function testForType(type, valuePrefix, valueSuffix, zone) { + let valueType = ICAL.design.vcard.value[type]; + let prefix = valuePrefix || ''; + let suffix = valueSuffix || ''; + let jcalvalue = prefix + jcal + suffix; + let icalvalue = prefix + ical + suffix.replace(':', ''); + let zoneName = zone || valueSuffix || "floating"; + + test(type + ' ' + zoneName + ' fromICAL/toICAL', function() { + assert.equal(valueType.fromICAL(icalvalue), jcalvalue); + assert.equal(valueType.toICAL(jcalvalue), icalvalue); + }); + + test(type + ' ' + zoneName + ' decorated/undecorated', function() { + let prop = new ICAL.Property(['anniversary', {}, type]); + let decorated = valueType.decorate(jcalvalue, prop); + let undecorated = valueType.undecorate(decorated); + + assert.hasProperties(decorated._time, props); + assert.equal(zoneName, decorated.zone.toString()); + assert.equal(undecorated, jcalvalue); + assert.equal(decorated.toICALString(), icalvalue); + }); + } + (only ? suite.only : suite)(jcal, function() { + + if (props.year || props.month || props.day) { + testForType('date-and-or-time'); + if (!props.hour && !props.minute && !props.second) { + testForType('date'); + } else { + testForType('date-time'); + } + } else if (props.hour || props.minute || props.second) { + if (!props.year && !props.month && !props.day) { + testForType('date-and-or-time', 'T'); + testForType('date-and-or-time', 'T', 'Z', 'UTC'); + testForType('date-and-or-time', 'T', '-08:00'); + testForType('date-and-or-time', 'T', '+08:00'); + testForType('time'); + testForType('time', null, 'Z', 'UTC'); + testForType('time', null, '-08:00'); + testForType('time', null, '+08:00'); + } else { + testForType('date-and-or-time', null); + testForType('date-and-or-time', null, 'Z', 'UTC'); + testForType('date-and-or-time', null, '-08:00'); + testForType('date-and-or-time', null, '+08:00'); + } + } + }); + } + testRoundtrip.only = function(jcal, ical, props) { + testRoundtrip(jcal, ical, props, true); + }; + + // dates + testRoundtrip('1985-04-12', '19850412', { + year: 1985, + month: 4, + day: 12, + hour: null, + minute: null, + second: null + }); + testRoundtrip('1985-04', '1985-04', { + year: 1985, + month: 4, + day: null, + hour: null, + minute: null, + second: null + }); + testRoundtrip('1985', '1985', { + year: 1985, + month: null, + day: null, + hour: null, + minute: null, + second: null + }); + testRoundtrip('--04-12', '--0412', { + year: null, + month: 4, + day: 12, + hour: null, + minute: null, + second: null + }); + testRoundtrip('--04', '--04', { + year: null, + month: 4, + day: null, + hour: null, + minute: null, + second: null + }); + testRoundtrip('---12', '---12', { + year: null, + month: null, + day: 12, + hour: null, + minute: null, + second: null + }); + + // times + testRoundtrip('23:20:50', '232050', { + year: null, + month: null, + day: null, + hour: 23, + minute: 20, + second: 50, + }); + testRoundtrip('23:20', '2320', { + year: null, + month: null, + day: null, + hour: 23, + minute: 20, + second: null, + }); + testRoundtrip('23', '23', { + year: null, + month: null, + day: null, + hour: 23, + minute: null, + second: null, + }); + testRoundtrip('-20:50', '-2050', { + year: null, + month: null, + day: null, + hour: null, + minute: 20, + second: 50, + }); + testRoundtrip('-20', '-20', { + year: null, + month: null, + day: null, + hour: null, + minute: 20, + second: null, + }); + testRoundtrip('--50', '--50', { + year: null, + month: null, + day: null, + hour: null, + minute: null, + second: 50, + }); + + // date-times + testRoundtrip('1985-04-12T23:20:50', '19850412T232050', { + year: 1985, + month: 4, + day: 12, + hour: 23, + minute: 20, + second: 50 + }); + testRoundtrip('1985-04-12T23:20', '19850412T2320', { + year: 1985, + month: 4, + day: 12, + hour: 23, + minute: 20, + second: null + }); + testRoundtrip('1985-04-12T23', '19850412T23', { + year: 1985, + month: 4, + day: 12, + hour: 23, + minute: null, + second: null + }); + testRoundtrip('--04-12T23:20', '--0412T2320', { + year: null, + month: 4, + day: 12, + hour: 23, + minute: 20, + second: null + }); + testRoundtrip('--04T23:20', '--04T2320', { + year: null, + month: 4, + day: null, + hour: 23, + minute: 20, + second: null + }); + testRoundtrip('---12T23:20', '---12T2320', { + year: null, + month: null, + day: 12, + hour: 23, + minute: 20, + second: null + }); + testRoundtrip('--04T23', '--04T23', { + year: null, + month: 4, + day: null, + hour: 23, + minute: null, + second: null + }); + }); + + suite('duration', function() { + setup(function() { + subject = subject.value.duration; + }); + + test('#(un)decorate', function() { + let undecorated = 'P15DT5H5M20S'; + let decorated = subject.decorate(undecorated); + assert.equal(subject.undecorate(decorated), undecorated); + }); + }); + + suite('float', function() { + setup(function() { + subject = subject.value.float; + }); + + test('#(from|to)ICAL', function() { + let original = '1.5'; + let fromICAL = subject.fromICAL(original); + + assert.equal(fromICAL, 1.5); + assert.equal(subject.toICAL(fromICAL), original); + }); + }); + + suite('integer', function() { + setup(function() { + subject = subject.value.integer; + }); + + test('#(from|to)ICAL', function() { + let original = '105'; + let fromICAL = subject.fromICAL(original); + + assert.equal(fromICAL, 105); + assert.equal(subject.toICAL(fromICAL), original); + }); + }); + + suite('period', function() { + setup(function() { + subject = subject.value.period; + }); + test('#(to|from)ICAL date/date (lenient)', function() { + let original = '19970101/19970102'; + ICAL.design.strict = false; + + let fromICAL = subject.fromICAL(original); + + assert.deepEqual( + fromICAL, + ['1997-01-01', '1997-01-02'] + ); + + assert.equal( + subject.toICAL(fromICAL), + original + ); + + ICAL.design.strict = true; + }); + + test('#(to|from)ICAL date/date', function() { + let original = '19970101T180000Z/19970102T070000Z'; + let fromICAL = subject.fromICAL(original); + + assert.deepEqual( + fromICAL, + ['1997-01-01T18:00:00Z', '1997-01-02T07:00:00Z'] + ); + + assert.equal( + subject.toICAL(fromICAL), + original + ); + }); + + test('#(un)decorate (date-time/duration)', function() { + let prop = new ICAL.Property(['date', { tzid: 'test' }]); + + let undecorated = ['1997-01-01T18:00:00', 'PT5H30M']; + let decorated = subject.decorate( + undecorated, + prop + ); + + assert.hasProperties( + decorated.start, + { + year: 1997, + day: 1, + month: 1, + hour: 18 + } + ); + + assert.equal(decorated.start.zone, timezone); + + assert.hasProperties( + decorated.duration, + { + hours: 5, + minutes: 30 + } + ); + + assert.deepEqual(subject.undecorate(decorated), undecorated); + }); + + test('#(un)decorate (date-time/date-time)', function() { + let prop = new ICAL.Property(['date', { tzid: 'test' }]); + + let undecorated = ['1997-01-01T18:00:00', '1998-01-01T17:00:00']; + let decorated = subject.decorate( + undecorated, + prop + ); + + assert.hasProperties( + decorated.start, + { + year: 1997, + day: 1, + month: 1, + hour: 18 + } + ); + + assert.hasProperties( + decorated.end, + { + year: 1998, + day: 1, + month: 1, + hour: 17 + } + ); + + + assert.equal(decorated.start.zone, timezone); + assert.equal(decorated.end.zone, timezone); + + assert.deepEqual(subject.undecorate(decorated), undecorated); + }); + + test('#(un)decorate (lenient, date/date)', function() { + ICAL.design.strict = false; + + let prop = new ICAL.Property(['date', { tzid: 'test' }]); + + let undecorated = ['1997-01-01', '1998-01-01']; + let decorated = subject.decorate( + undecorated, + prop + ); + + assert.hasProperties( + decorated.start, + { + year: 1997, + day: 1, + month: 1, + isDate: true + } + ); + + assert.hasProperties( + decorated.end, + { + year: 1998, + day: 1, + month: 1, + isDate: true + } + ); + + assert.deepEqual(subject.undecorate(decorated), undecorated); + + ICAL.design.strict = true; + }); + + test('#(un)decorate (date-time/duration)', function() { + let prop = new ICAL.Property(['date', { tzid: 'test' }]); + + let undecorated = ['1997-01-01T18:00:00', 'PT5H30M']; + let decorated = subject.decorate( + undecorated, + prop + ); + + assert.hasProperties( + decorated.start, + { + year: 1997, + day: 1, + month: 1, + hour: 18 + } + ); + + assert.equal(decorated.start.zone, timezone); + + assert.hasProperties( + decorated.duration, + { + hours: 5, + minutes: 30 + } + ); + + assert.deepEqual(subject.undecorate(decorated), undecorated); + }); + }); + + suite('recur', function() { + setup(function() { + subject = subject.value.recur; + }); + + test('#(to|from)ICAL', function() { + let original = 'FREQ=MONTHLY;UNTIL=20121112T131415;COUNT=1'; + let fromICAL = subject.fromICAL(original); + + assert.deepEqual(fromICAL, { + freq: 'MONTHLY', + until: '2012-11-12T13:14:15', + count: 1 + }); + + assert.equal( + subject.toICAL(fromICAL), + original + ); + }); + + test('#(un)decorate', function() { + let undecorated = { freq: "MONTHLY", byday: ["MO", "TU", "WE", "TH", "FR"], until: "2012-10-12" }; + let decorated = subject.decorate(undecorated); + + assert.instanceOf(decorated, ICAL.Recur); + + assert.hasProperties( + decorated, + { + freq: 'MONTHLY', + parts: { + BYDAY: ['MO', 'TU', 'WE', 'TH', 'FR'] + } + } + ); + + assert.hasProperties( + decorated.until, + { + year: 2012, + month: 10, + day: 12 + } + ); + + assert.deepEqual(subject.undecorate(decorated), undecorated); + }); + }); + + suite('utc-offset', function() { + setup(function() { + subject = subject.value['utc-offset']; + }); + + test('#(to|from)ICAL without seconds', function() { + let original = '-0500'; + let fromICAL = subject.fromICAL(original); + + assert.equal(fromICAL, '-05:00'); + assert.equal( + subject.toICAL(fromICAL), + original + ); + }); + + test('#(to|from)ICAL with seconds', function() { + let original = '+054515'; + let fromICAL = subject.fromICAL(original); + + assert.equal(fromICAL, '+05:45:15'); + assert.equal( + subject.toICAL(fromICAL), + original + ); + }); + + test('#(un)decorate', function() { + let undecorated = '-05:00'; + let decorated = subject.decorate(undecorated); + + assert.equal(decorated.hours, 5, 'hours'); + assert.equal(decorated.factor, -1, 'factor'); + + assert.equal( + subject.undecorate(decorated), + undecorated + ); + }); + }); + + suite('utc-offset (vcard3)', function() { + setup(function() { + subject = ICAL.design.vcard3.value['utc-offset']; + }); + + test('#(to|from)ICAL', function() { + let original = '-05:00'; + let fromICAL = subject.fromICAL(original); + + assert.equal(fromICAL, '-05:00'); + assert.equal( + subject.toICAL(fromICAL), + original + ); + }); + + test('#(un)decorate', function() { + let undecorated = '-05:00'; + let decorated = subject.decorate(undecorated); + + assert.equal(decorated.hours, 5, 'hours'); + assert.equal(decorated.factor, -1, 'factor'); + + assert.equal( + subject.undecorate(decorated), + undecorated + ); + }); + }); + + suite("unknown and default values", function() { + test("unknown x-prop", function() { + let prop = new ICAL.Property("x-wr-calname"); + assert.equal(prop.type, "unknown"); + + prop = ICAL.Property.fromString("X-WR-CALNAME:value"); + assert.equal(prop.type, "unknown"); + }); + + test("unknown iana prop", function() { + let prop = new ICAL.Property("standardized"); + assert.equal(prop.type, "unknown"); + + prop = ICAL.Property.fromString("STANDARDIZED:value"); + assert.equal(prop.type, "unknown"); + }); + + test("known text type", function() { + let prop = new ICAL.Property("description"); + assert.equal(prop.type, "text"); + + prop = ICAL.Property.fromString("DESCRIPTION:value"); + assert.equal(prop.type, "text"); + }); + + test("encoded text value roundtrip", function() { + let prop = new ICAL.Property("description"); + prop.setValue("hello, world"); + let propVal = prop.toICALString(); + assert.equal(propVal, "DESCRIPTION:hello\\, world"); + + prop = ICAL.Property.fromString(propVal); + assert.equal(prop.getFirstValue(), "hello, world"); + }); + + test("encoded unknown value roundtrip", function() { + let prop = new ICAL.Property("x-wr-calname"); + prop.setValue("hello, world"); + let propVal = prop.toICALString(); + assert.equal(propVal, "X-WR-CALNAME:hello, world"); + + prop = ICAL.Property.fromString(propVal); + assert.equal(prop.getFirstValue(), "hello, world"); + }); + + test("encoded unknown value from string", function() { + let prop = ICAL.Property.fromString("X-WR-CALNAME:hello\\, world"); + assert.equal(prop.getFirstValue(), "hello\\, world"); + }); + + suite("registration", function() { + test("newly registered property", function() { + let prop = new ICAL.Property("nonstandard"); + assert.equal(prop.type, "unknown"); + + ICAL.design.defaultSet.property.nonstandard = { + defaultType: "date-time" + }; + + prop = new ICAL.Property("nonstandard"); + assert.equal(prop.type, "date-time"); + }); + + test("unknown value type", function() { + let prop = ICAL.Property.fromString("X-PROP;VALUE=FUZZY:WARM"); + assert.equal(prop.name, "x-prop"); + assert.equal(prop.type, "fuzzy"); + assert.equal(prop.getFirstValue(), "WARM"); + prop.setValue("FREEZING"); + assert.equal(prop.getFirstValue(), "FREEZING"); + }); + + test("newly registered value type", function() { + ICAL.design.defaultSet.value.fuzzy = { + fromICAL: function(aValue) { + return aValue.toLowerCase(); + }, + toICAL: function(aValue) { + return aValue.toUpperCase(); + } + }; + + let prop = ICAL.Property.fromString("X-PROP;VALUE=FUZZY:WARM"); + assert.equal(prop.name, "x-prop"); + assert.equal(prop.getFirstValue(), "warm"); + assert.match(prop.toICALString(), /WARM/); + }); + + test("newly registered parameter", function() { + let prop = ICAL.Property.fromString("X-PROP;VALS=a,b,c:def"); + let param = prop.getParameter("vals"); + assert.equal(param, "a,b,c"); + + ICAL.design.defaultSet.param.vals = { multiValue: "," }; + + prop = ICAL.Property.fromString("X-PROP;VALS=a,b,c:def"); + param = prop.getParameter("vals"); + assert.deepEqual(param, ["a", "b", "c"]); + }); + }); + }); + }); +}); diff --git a/test/duration_test.js b/test/duration_test.js new file mode 100644 index 0000000..83e46b1 --- /dev/null +++ b/test/duration_test.js @@ -0,0 +1,177 @@ +suite('ical/duration', function() { + test('#clone', function() { + let subject = ICAL.Duration.fromData({ + weeks: 1, + days: 2, + hours: 3, + minutes: 4, + seconds: 5, + isNegative: true + }); + + let expected = { + weeks: 1, + days: 2, + hours: 3, + minutes: 4, + seconds: 5, + isNegative: true + }; + + let expected2 = { + weeks: 6, + days: 7, + hours: 8, + minutes: 9, + seconds: 10, + isNegative: true + }; + + let subject2 = subject.clone(); + assert.hasProperties(subject, expected, 'base object unchanged'); + assert.hasProperties(subject2, expected, 'cloned object unchanged'); + + for (let k in expected2) { + subject2[k] = expected2[k]; + } + + assert.hasProperties(subject, expected, 'base object unchanged'); + assert.hasProperties(subject2, expected2, 'cloned object changed'); + }); + + test('#reset', function() { + let expected = { + weeks: 1, + days: 2, + hours: 3, + minutes: 4, + seconds: 5, + isNegative: true + }; + let subject = new ICAL.Duration(expected); + assert.hasProperties(subject, expected); + + subject.reset(); + + assert.hasProperties(subject, { + weeks: 0, + days: 0, + hours: 0, + minutes: 0, + seconds: 0, + isNegative: false + }); + + assert.equal(subject.toString(), "PT0S"); + }); + + suite('#normalize', function() { + function verify(name, str, data) { + test(name, function() { + let subject = new ICAL.Duration(); + for (let k in data) { + subject[k] = data[k]; + } + subject.normalize(); + assert.equal(subject.toString(), str); + assert.equal(subject.toICALString(), str); + }); + } + + verify('weeks and day => days', 'P50D', { + weeks: 7, + days: 1 + }); + verify('days => week', 'P2W', { + days: 14 + }); + verify('days and weeks => week', 'P4W', { + weeks: 2, + days: 14 + }); + verify('seconds => everything', 'P1DT1H1M1S', { + seconds: 86400 + 3600 + 60 + 1 + }); + }); + + suite("#compare", function() { + function verify(str, a, b, cmp) { + test(str, function() { + let dur_a = ICAL.Duration.fromString(a); + let dur_b = ICAL.Duration.fromString(b); + assert.equal(dur_a.compare(dur_b), cmp); + }); + } + + verify('a>b', 'PT3H', 'PT1S', 1); + verify('a date', function() { + // ensure we are in the right time type + property.resetType('date-time'); + + subject[eventProp] = time = new ICAL.Time({ + year: 2013, + month: 1, + day: 1, + isDate: true + }); + + assert.equal(property.type, 'date'); + + assert.include( + property.toICALString(), + time.toICALString() + ); + }); + + test('type date -> date-time', function() { + // ensure we are in the right time type + property.resetType('date'); + + subject[eventProp] = time = new ICAL.Time({ + year: 2013, + month: 1, + day: 1, + hour: 3, + isDate: false + }); + + assert.equal(property.type, 'date-time'); + + assert.include( + property.toICALString(), + time.toICALString() + ); + }); + } + + dateFields.forEach(function(field) { + suite( + field[0], + verifyTzidHandling.bind(this, field[0], field[1]) + ); + }); + + }); + + suite('initializer', function() { + test('only with component', function() { + assert.equal(subject.component, primaryItem); + assert.instanceOf(subject.rangeExceptions, Array); + }); + + test('with exceptions from the component\'s parent if not specified in options', function() { + subject = new ICAL.Event(primaryItem); + + let expected = Object.create(null); + exceptions.forEach(function(exception) { + expected[exception.getFirstPropertyValue('recurrence-id').toString()] = new ICAL.Event(exception); + }); + + assert.deepEqual(subject.exceptions, expected); + }); + + test('with exceptions specified in options if any', function() { + subject = new ICAL.Event(primaryItem, { + exceptions: exceptions.slice(1) + }); + + let expected = Object.create(null); + exceptions.slice(1).forEach(function(exception) { + expected[exception.getFirstPropertyValue('recurrence-id').toString()] = new ICAL.Event(exception); + }); + + assert.deepEqual(subject.exceptions, expected); + }); + + test('with strict exceptions', function() { + subject = new ICAL.Event(primaryItem, { + strictExceptions: true + }); + assert.ok(subject.strictExceptions); + }); + }); + + suite('creating a event', function() { + setup(function() { + subject = new ICAL.Event(); + }); + + test('initial state', function() { + assert.instanceOf(subject.component, ICAL.Component); + assert.equal(subject.component.name, 'vevent'); + }); + + suite('roundtrip', function() { + let props; + + suiteSetup(function() { + props = { + uid: 'zfoo', + summary: 'sum', + description: 'desc', + startDate: new ICAL.Time({ + year: 2012, + month: 1, + day: 1, + hour: 5 + }), + endDate: new ICAL.Time({ + year: 2012, + month: 1, + day: 1, + hour: 10 + }), + location: 'place', + organizer: 'SJL', + recurrenceId: new ICAL.Time({ + year: 2012, + month: 1, + day: 1 + }) + }; + }); + + test('setters', function() { + for (let key in props) { + subject[key] = props[key]; + assert.equal(subject[key], props[key], key); + } + }); + + test('to string roundtrip', function() { + let aComp = new ICAL.Component(ICAL.parse(icsDataRecurInstances)); + let aEvent = new ICAL.Event(aComp); + + let bComp = new ICAL.Component( + ICAL.parse(aComp.toString()) + ); + + let bEvent = new ICAL.Event(bComp); + assert.equal(aEvent.toString(), bEvent.toString()); + }); + }); + + }); + + suite('#getOccurrenceDetails', function() { + setup(function() { + exceptions.forEach(subject.relateException, subject); + }); + + suite('RANGE=THISANDFUTURE', function() { + test('starts earlier ends later', function() { + let exception = rangeException(1); + let rid = exception.recurrenceId; + + exception.startDate = rid.clone(); + exception.endDate = rid.clone(); + + // starts 2 hours & 2 min early + exception.startDate.hour -= 2; + exception.startDate.minute += 2; + + // starts 1 hour - 2 min later + exception.endDate.hour += 1; + exception.endDate.minute -= 2; + + subject.relateException(exception); + + + // create a time that has no exception + // but past the RID. + let occurs = rid.clone(); + occurs.day += 3; + occurs.hour = 13; + occurs.minutes = 15; + + // Run the following tests twice, the second time around the results + // will be cached. + for (let i = 0; i < 2; i++) { + let suffix = (i == 1 ? " (cached)" : ""); + let details = subject.getOccurrenceDetails( + occurs + ); + + assert.ok(details, 'has details' + suffix); + assert.equal(details.item, exception, 'uses exception' + suffix); + + + let expectedStart = occurs.clone(); + let expectedEnd = occurs.clone(); + + // same offset (in different day) as the difference + // in the original exception.d + expectedStart.hour -= 2; + expectedStart.minute += 2; + expectedEnd.hour += 1; + expectedEnd.minute -= 2; + + assert.deepEqual( + details.startDate.toJSDate(), + expectedStart.toJSDate(), + 'start time offset' + suffix + ); + + assert.deepEqual( + details.endDate.toJSDate(), + expectedEnd.toJSDate(), + 'end time offset' + suffix + ); + } + }); + }); + + test('exception', function() { + let time = exceptions[0].getFirstPropertyValue('recurrence-id'); + + let start = exceptions[0].getFirstPropertyValue('dtstart'); + let end = exceptions[0].getFirstPropertyValue('dtend'); + + let result = subject.getOccurrenceDetails(time); + + assert.equal( + result.recurrenceId.toString(), + time.toString(), + 'recurrence id' + ); + + assert.equal( + result.endDate.toString(), + end.toString(), + 'end date' + ); + + assert.equal( + result.startDate.toString(), + start.toString(), + 'start date' + ); + + assert.deepEqual( + result.item.component.toJSON(), + exceptions[0].toJSON(), + 'item' + ); + }); + + test('non-exception', function() { + + let time = new ICAL.Time({ + year: 2012, + month: 7, + day: 12 + }); + + let end = time.clone(); + end.addDuration(subject.duration); + + let result = subject.getOccurrenceDetails(time); + + assert.equal( + result.startDate.toString(), + time.toString(), + 'start date' + ); + + assert.equal( + result.endDate.toString(), + end.toString() + ); + + assert.equal( + result.recurrenceId.toString(), + time.toString() + ); + + assert.equal(result.item, subject); + }); + + test('iterate over exceptions', function() { + for (let counter = 0, iterator = subject.iterator(); counter < 2; counter++) { + let result = subject.getOccurrenceDetails(iterator.next()); + let exception = exceptions[counter]; + + assert.equal( + result.endDate.toString(), + exception.getFirstPropertyValue('dtend').toString(), + 'end date' + ); + + assert.equal( + result.startDate.toString(), + exception.getFirstPropertyValue('dtstart').toString(), + 'start date' + ); + + assert.deepEqual( + result.item.component.toJSON(), + exception.toJSON(), + 'item' + ); + } + }); + }); + + suite('#recurrenceTypes', function() { + + suite('multiple rrules', function() { + let icsData; + + suiteSetup(async function() { + icsData = await testSupport.loadSample('multiple_rrules.ics'); + }); + + test('result', function() { + let comp = new ICAL.Component(ICAL.parse(icsData)); + subject = new ICAL.Event(comp.getFirstSubcomponent('vevent')); + + let expected = { + 'MONTHLY': true, + 'WEEKLY': true + }; + + assert.deepEqual(subject.getRecurrenceTypes(), expected); + }); + }); + + test('no rrule', function() { + subject.component.removeProperty('rrule'); + + assert.deepEqual( + subject.getRecurrenceTypes(), + {} + ); + }); + }); + + suite('#relateException', function() { + + test('trying to relate an exception to an exception', function() { + let exception = new ICAL.Event(exceptions[0]); + + assert.throws(function() { + exception.relateException(exceptions[1]); + }); + }); + + test('trying to relate unrelated component (without strict)', function() { + let exception = exceptions[0]; + let prop = exception.getFirstProperty('uid'); + prop.setValue('foo'); + + subject.relateException(exception); + }); + + test('trying to relate unrelated component (with strict)', function() { + let exception = exceptions[0]; + let prop = exception.getFirstProperty('uid'); + prop.setValue('foo'); + + subject.strictExceptions = true; + assert.throws(function() { + subject.relateException(exception); + }, /unrelated/); + }); + + test('from ical component', function() { + subject = new ICAL.Event(primaryItem, { exceptions: [] }); + let exception = exceptions[0]; + subject.relateException(exception); + + let expected = Object.create(null); + expected[exception.getFirstPropertyValue('recurrence-id').toString()] = new ICAL.Event(exception); + + assert.deepEqual(subject.exceptions, expected); + assert.lengthOf(subject.rangeExceptions, 0, 'does not add range'); + }); + + suite('with RANGE=THISANDFUTURE', function() { + function exceptionTime(index, mod) { + mod = mod || 0; + + + let item = subject.rangeExceptions[index]; + let utc = item[0]; + let time = new ICAL.Time(); + time.fromUnixTime(utc + mod); + + return time; + } + + let list; + + setup(function() { + list = [ + rangeException(3), + rangeException(10), + rangeException(1) + ]; + + list.forEach(subject.relateException.bind(subject)); + assert.lengthOf(subject.rangeExceptions, 3); + }); + + function nthRangeException(nth) { + return subject.rangeExceptions[nth]; + } + + function listDetails(obj) { + return [ + obj.recurrenceId.toUnixTime(), + obj.recurrenceId.toString() + ]; + } + + test('ranges', function() { + let expected = [ + listDetails(list[2]), // 1st + listDetails(list[0]), // 2nd + listDetails(list[1]) // 3rd + ]; + + assert.deepEqual( + subject.rangeExceptions, + expected + ); + }); + + test('#findRangeException', function() { + let before = exceptionTime(0, -1); + let on = exceptionTime(0); + let first = exceptionTime(0, 1); + let second = exceptionTime(1, 30); + let third = exceptionTime(2, 100000); + + assert.ok( + !subject.findRangeException(before), + 'find before range' + ); + + assert.ok( + !subject.findRangeException(on), + 'day of exception does not need a modification' + ); + + assert.equal( + subject.findRangeException(first), + nthRangeException(0)[1], + 'finds first item' + ); + + assert.equal( + subject.findRangeException(second), + nthRangeException(1)[1], + 'finds second item' + ); + + assert.equal( + subject.findRangeException(third), + nthRangeException(2)[1], + 'finds third item' + ); + }); + }); + }); + + suite('#isRecurring', function() { + test('when is primary recurring item', function() { + assert.isTrue(subject.isRecurring()); + }); + + test('when is exception', function() { + subject = new ICAL.Event(exceptions[0]); + assert.isFalse(subject.isRecurring()); + }); + }); + + suite('#modifiesFuture', function() { + + test('without range or exception', function() { + assert.isFalse(subject.isRecurrenceException()); + assert.isFalse(subject.modifiesFuture()); + }); + + test('with range and exception', function() { + subject.component + .addPropertyWithValue( + 'recurrence-id', + ICAL.Time.fromJSDate(new Date())) + .setParameter( + 'range', + ICAL.Event.THISANDFUTURE); + + assert.isTrue(subject.modifiesFuture()); + }); + }); + + suite('#isRecurrenceException', function() { + test('when is primary recurring item', function() { + assert.isFalse(subject.isRecurrenceException()); + }); + + test('when is exception', function() { + subject = new ICAL.Event(exceptions[0]); + assert.isTrue(subject.isRecurrenceException()); + }); + }); + + suite('date props', function() { + [ + ['dtstart', 'startDate'], + ['dtend', 'endDate'] + ].forEach(function(dateType) { + let ical = dateType[0]; + let prop = dateType[1]; + let timeProp; + let changeTime; + + suite('#' + prop, function() { + let tzid = 'America/Denver'; + + setup(function() { + timeProp = primaryItem.getFirstProperty(ical); + }); + + test('get', function() { + let expected = timeProp.getFirstValue(ical); + assert.deepEqual(expected, subject[prop]); + }); + + function changesTzid(newTzid) { + assert.notEqual( + timeProp.getFirstValue().zone.tzid, + changeTime.zone.tzid, + 'zones are different' + ); + + subject[prop] = changeTime; + assert.equal( + newTzid, + timeProp.getParameter('tzid'), + 'removes timezone id' + ); + } + + test('changing timezone from America/Los_Angeles', function() { + changeTime = new ICAL.Time({ + year: 2012, + month: 1, + timezone: tzid + }); + + changesTzid(tzid); + }); + + test('changing timezone from floating to UTC', function() { + timeProp.setValue(new ICAL.Time({ + year: 2012, + month: 1 + })); + + changeTime = new ICAL.Time({ + year: 2012, + month: 1, + timezone: 'Z' + }); + + changesTzid(undefined); + }); + + test('changing timezone to floating', function() { + timeProp.setValue(new ICAL.Time({ + year: 2012, + month: 1, + timezone: 'Z' + })); + + changeTime = new ICAL.Time({ + year: 2012, + month: 1 + }); + + changesTzid(undefined); + }); + + }); + + }); + }); + + suite('remaining properties', function() { + function testProperty(prop, changeval) { + test('#' + prop, function() { + let expected = primaryItem.getFirstPropertyValue(prop); + assert.deepEqual(subject[prop], expected); + + subject[prop] = changeval; + assert.equal(primaryItem.getFirstPropertyValue(prop), changeval); + }); + } + + testProperty('location', 'other'); + testProperty('summary', 'other'); + testProperty('description', 'other'); + testProperty('organizer', 'other'); + testProperty('uid', 'other'); + testProperty('sequence', 123); + testProperty('color', 'turquoise'); + + test('#duration', function() { + let end = subject.endDate; + let start = subject.startDate; + let duration = end.subtractDate(start); + + assert.deepEqual( + subject.duration.toString(), + duration.toString() + ); + }); + + test('#attendees', function() { + let props = primaryItem.getAllProperties('attendee'); + assert.deepEqual(subject.attendees, props); + }); + + test('#recurrenceId', function() { + subject = new ICAL.Event(exceptions[0]); + let expected = exceptions[0].getFirstPropertyValue('recurrence-id'); + let changeval = exceptions[1].getFirstPropertyValue('recurrence-id'); + assert.deepEqual(subject.recurrenceId, expected); + + subject.recurrenceId = changeval; + assert.deepEqual(subject.component.getFirstPropertyValue('recurrence-id'), changeval); + + let tzid = 'America/New_York'; + let changeval2 = new ICAL.Time({ + year: 2012, + month: 1, + day: 1, + hour: 12, + minute: 13, + second: 14, + timezone: tzid + }); + + subject.recurrenceId = changeval2; + assert.deepEqual(subject.component.getFirstProperty('recurrence-id').getParameter("tzid"), tzid); + }); + }); + + suite('#iterator', function() { + test('with start time', function() { + let start = subject.startDate; + let time = new ICAL.Time({ + day: start.da + 1, + month: start.month, + year: start.year + }); + + let iterator = subject.iterator(time); + assert.deepEqual(iterator.last.toString(), time.toString()); + assert.instanceOf(iterator, ICAL.RecurExpansion); + }); + + test('without a start time', function() { + let iterator = subject.iterator(); + + assert.equal( + iterator.last.toString(), + subject.startDate.toString() + ); + }); + }); + + suite('duration instead of dtend', function() { + let icsData; + + suiteSetup(async function() { + icsData = await testSupport.loadSample('duration_instead_of_dtend.ics'); + }); + + test('result', function() { + subject = new ICAL.Component(ICAL.parse(icsData)); + subject = new ICAL.Event(subject.getFirstSubcomponent('vevent')); + assert.equal(subject.startDate.toString(), new ICAL.Time({ + year: 2012, + month: 6, + day: 30, + hour: 6, + isDate: false, + timezone: testTzid + }).toString()); + + assert.equal(subject.endDate.toString(), new ICAL.Time({ + year: 2012, + month: 7, + day: 1, + hour: 6, + isDate: false, + timezone: testTzid + }).toString()); + + assert.equal(subject.duration.toString(), 'P1D'); + }); + + test('set', function() { + let comp = new ICAL.Component(ICAL.parse(icsData)); + subject = new ICAL.Event(comp.getFirstSubcomponent('vevent')); + + assert.include(subject.toString(), "DURATION"); + assert.notInclude(subject.toString(), "DTEND"); + + subject.endDate = new ICAL.Time({ + year: 2012, + month: 7, + day: 2, + hour: 6, + isDate: false, + timezone: subject.startDate.zone + }); + + assert.equal(subject.duration.toString(), 'P2D'); + assert.equal(subject.endDate.toString(), new ICAL.Time({ + year: 2012, + month: 7, + day: 2, + hour: 6, + isDate: false, + timezone: testTzid + }).toString()); + + assert.notInclude(subject.toString(), "DURATION"); + assert.include(subject.toString(), "DTEND"); + }); + }); + + suite('only a dtstart date', function() { + let icsData; + + suiteSetup(async function() { + icsData = await testSupport.loadSample('only_dtstart_date.ics'); + }); + + test('result', function() { + let comp = new ICAL.Component(ICAL.parse(icsData)); + subject = new ICAL.Event(comp.getFirstSubcomponent('vevent')); + assert.equal(subject.startDate.toString(), new ICAL.Time({ + year: 2012, + month: 6, + day: 30, + hour: 0, + isDate: true, + timezone: testTzid + }).toString()); + + assert.equal(subject.endDate.toString(), new ICAL.Time({ + year: 2012, + month: 7, + day: 1, + hour: 6, + isDate: true, + timezone: testTzid + }).toString()); + + assert.equal(subject.duration.toString(), 'P1D'); + }); + }); + + suite('only a dtstart time', function() { + let icsData; + + suiteSetup(async function() { + icsData = await testSupport.loadSample('only_dtstart_time.ics'); + }); + + test('result', function() { + let comp = new ICAL.Component(ICAL.parse(icsData)); + subject = new ICAL.Event(comp.getFirstSubcomponent('vevent')); + assert.equal(subject.startDate.toString(), new ICAL.Time({ + year: 2012, + month: 6, + day: 30, + hour: 6, + isDate: false, + timezone: testTzid + }).toString()); + + assert.equal(subject.endDate.toString(), new ICAL.Time({ + year: 2012, + month: 6, + day: 30, + hour: 6, + isDate: false, + timezone: testTzid + }).toString()); + + assert.equal(subject.duration.toString(), 'PT0S'); + }); + }); + + suite('dtend instead of duration', function() { + let icsData; + + suiteSetup(async function() { + icsData = await testSupport.loadSample('minimal.ics'); + }); + + test('result with different timezones', function() { + subject = (new ICAL.Component(ICAL.parse(icsData))) + .getFirstSubcomponent('vevent'); + // 3 hours ahead of L.A. + subject.updatePropertyWithValue('dtstart', ICAL.Time.fromData({ + year: 2012, + month: 1, + day: 1, + hour: 10, + minute: 20, + timezone: 'America/New_York' + })); + subject.updatePropertyWithValue('dtend', ICAL.Time.fromData({ + year: 2012, + month: 1, + day: 1, + hour: 12, + minute: 50, + timezone: 'America/Los_Angeles' + })); + + subject = new ICAL.Event(subject); + assert.equal(subject.startDate.toString(), ICAL.Time.fromData({ + year: 2012, + month: 1, + day: 1, + hour: 10, + minute: 20, + timezone: 'America/New_York', + }).toString()); + + assert.equal(subject.endDate.toString(), ICAL.Time.fromData({ + year: 2012, + month: 1, + day: 1, + hour: 12, + minute: 50, + timezone: 'America/Los_Angeles' + }).toString()); + + assert.equal(subject.duration.toString(), 'PT5H30M'); + }); + + test('set', function() { + let comp = new ICAL.Component(ICAL.parse(icsData)); + subject = new ICAL.Event(comp.getFirstSubcomponent('vevent')); + + assert.notInclude(subject.toString(), "DURATION"); + assert.include(subject.toString(), "DTEND"); + + subject.duration = ICAL.Duration.fromString("P2D"); + + assert.equal(subject.duration.toString(), 'P2D'); + assert.equal(subject.endDate.toString(), new ICAL.Time({ + year: 2012, + month: 7, + day: 2, + hour: 6, + isDate: false, + timezone: testTzid + }).toString()); + + assert.include(subject.toString(), "DURATION"); + assert.notInclude(subject.toString(), "DTEND"); + }); + }); +}); diff --git a/test/failure_test.js b/test/failure_test.js new file mode 100644 index 0000000..b1d408f --- /dev/null +++ b/test/failure_test.js @@ -0,0 +1,67 @@ +import assert from "assert"; + + +/** + * The tests in this suite are known to fail, due to a bug in the library. If the tests here start + * failing in the sense of mocha, then the test is passing and you have either: + * + * 1) Fixed the bug (yay! remove the test) + * 2) Triggered some unknown underlying issue (boo! investigate) + * + * When adding something here, make sure to link the issue. + */ +suite('Known failures', function() { + function testKnownFailure(message, testFn, only) { + let runner = only ? test.only : test; + runner(message, function(done) { + try { + testFn(done); + done(new Error("Expected test fo fail")); + } catch (e) { + if (e instanceof assert.AssertionError) { + this.skip(); + } else { + done(e); + } + } + }); + } + testKnownFailure.only = function(message, testFn) { + return testKnownFailure(message, testFn, true); + }; + + // Escaped parameters are not correctly parsed + // Please see https://github.com/kewisch/ical.js/issues/669 + testKnownFailure('Parameter escaping', function() { + let subject = ICAL.Property.fromString(`ATTENDEE;CN="Z\\;":mailto:z@example.org`); + assert.equal(subject.getParameter("cn"), "Z\\;"); + assert.equal(subject.getFirstValue(), "mailto:z@example.org"); + }); + + // Quoted multi-value parameters leak into the value + // Please see https://github.com/kewisch/ical.js/issues/634 + testKnownFailure('with quoted multi-value parameter', function() { + let attendee = ICAL.Property.fromString( + 'ATTENDEE;MEMBER=' + + '"mailto:mygroup@localhost",' + + '"mailto:mygroup2@localhost",' + + '"mailto:mygroup3@localhost":' + + 'mailto:user2@localhost' + ); + + let expected = [ + 'attendee', + { + member: [ + 'mailto:mygroup@localhost', + 'mailto:mygroup2@localhost', + 'mailto:mygroup3@localhost' + ] + }, + 'cal-address', + 'mailto:user2@localhost' + ]; + + assert.deepEqual(attendee.toJSON(), expected); + }); +}); diff --git a/test/helper_test.js b/test/helper_test.js new file mode 100644 index 0000000..3a3413b --- /dev/null +++ b/test/helper_test.js @@ -0,0 +1,160 @@ +suite('ICAL.helpers', function() { + + suite('#clone', function() { + let subject = ICAL.helpers.clone; + test('some primatives', function() { + assert.equal(subject(null, false), null); + assert.equal(subject(123, false), 123); + assert.equal(subject(null, true), null); + assert.equal(subject(123, true), 123); + }); + + test('a date', function() { + let date = new Date(2015, 1, 1); + let time = date.getTime(); + let copy = subject(date, false); + + copy.setYear(2016); + assert.notEqual(time, copy.getTime()); + }); + + test('clonable', function() { + let obj = { clone: function() { return "test"; } }; + assert.equal(subject(obj, false), "test"); + }); + + test('shallow array', function() { + let obj = { v: 2 }; + let arr = [obj, 2, 3]; + + let result = subject(arr, false); + assert.deepEqual(result, [{ v: 2 }, 2, 3]); + obj.v = 3; + assert.deepEqual(result, [{ v: 3 }, 2, 3]); + }); + + test('deep array', function() { + let obj = { v: 2 }; + let arr = [obj, 2, 3]; + + let result = subject(arr, true); + assert.deepEqual(result, [{ v: 2 }, 2, 3]); + obj.v = 3; + assert.deepEqual(result, [{ v: 2 }, 2, 3]); + }); + + test('shallow object', function() { + let deepobj = { v: 2 }; + let obj = { a: deepobj, b: 2 }; + + let result = subject(obj, false); + assert.deepEqual(result, { a: { v: 2 }, b: 2 }); + deepobj.v = 3; + assert.deepEqual(result, { a: { v: 3 }, b: 2 }); + }); + + test('deep object', function() { + let deepobj = { v: 2 }; + let obj = { a: deepobj, b: 2 }; + + let result = subject(obj, true); + assert.deepEqual(result, { a: { v: 2 }, b: 2 }); + deepobj.v = 3; + assert.deepEqual(result, { a: { v: 2 }, b: 2 }); + }); + }); + + suite('#pad2', function() { + let subject = ICAL.helpers.pad2; + + test('with string', function() { + assert.equal(subject(""), "00"); + assert.equal(subject("1"), "01"); + assert.equal(subject("12"), "12"); + assert.equal(subject("123"), "123"); + }); + + test('with number', function() { + assert.equal(subject(0), "00"); + assert.equal(subject(1), "01"); + assert.equal(subject(12), "12"); + assert.equal(subject(123), "123"); + }); + + test('with boolean', function() { + assert.equal(subject(true), "true"); + }); + }); + + suite('#foldline', function() { + let subject = ICAL.helpers.foldline; + + test('empty values', function() { + assert.strictEqual(subject(null), ""); + assert.strictEqual(subject(""), ""); + }); + + // Most other cases are covered by other tests + }); + + suite('#updateTimezones', function() { + let subject = ICAL.helpers.updateTimezones; + let cal; + + suiteSetup(async function() { + let data = await testSupport.loadSample('minimal.ics'); + cal = new ICAL.Component(ICAL.parse(data)); + + data = await testSupport.loadSample('timezones/America/Atikokan.ics'); + ICAL.TimezoneService.register( + (new ICAL.Component(ICAL.parse(data))).getFirstSubcomponent("vtimezone") + ); + }); + + suiteTeardown(function() { + ICAL.TimezoneService.reset(); + }); + + test('timezones already correct', function() { + let vtimezones; + vtimezones = cal.getAllSubcomponents("vtimezone"); + assert.strictEqual(vtimezones.length, 1); + assert.strictEqual( + vtimezones[0].getFirstProperty("tzid").getFirstValue(), + "America/Los_Angeles" + ); + }); + + test('remove extra timezones', function() { + let vtimezones; + cal.addSubcomponent( + ICAL.TimezoneService.get("America/Atikokan").component + ); + vtimezones = cal.getAllSubcomponents("vtimezone"); + assert.strictEqual(vtimezones.length, 2); + + vtimezones = subject(cal).getAllSubcomponents("vtimezone"); + assert.strictEqual(vtimezones.length, 1); + assert.strictEqual( + vtimezones[0].getFirstProperty("tzid").getFirstValue(), + "America/Los_Angeles" + ); + }); + + test('add missing timezones', function() { + let vtimezones; + cal.getFirstSubcomponent("vevent") + .getFirstProperty("dtend").setParameter("tzid", "America/Atikokan"); + vtimezones = cal.getAllSubcomponents("vtimezone"); + assert(vtimezones.length, 1); + + vtimezones = subject(cal).getAllSubcomponents("vtimezone"); + assert.strictEqual(vtimezones.length, 2); + }); + + test('return non-vcalendar components unchanged', function() { + let vevent = cal.getFirstSubcomponent("vevent"); + assert.deepEqual(subject(vevent), vevent); + }); + }); +}); diff --git a/test/parse_test.js b/test/parse_test.js new file mode 100644 index 0000000..8a94f68 --- /dev/null +++ b/test/parse_test.js @@ -0,0 +1,317 @@ +suite('parserv2', function() { + + let subject; + setup(function() { + subject = ICAL.parse; + }); + + /** + * Full parser tests fetch two resources + * (one to parse, one is expected + */ + suite('full parser tests', function() { + let root = 'test/parser/'; + let list = [ + // icalendar tests + 'rfc.ics', + 'single_empty_vcalendar.ics', + 'property_params.ics', + 'newline_junk.ics', + 'unfold_properties.ics', + 'quoted_params.ics', + 'multivalue.ics', + 'values.ics', + 'recur.ics', + 'base64.ics', + 'dates.ics', + 'time.ics', + 'boolean.ics', + 'float.ics', + 'integer.ics', + 'period.ics', + 'utc_offset.ics', + 'component.ics', + 'tzid_with_gmt.ics', + 'multiple_root_components.ics', + 'grouped.ics', + + // vcard tests + 'vcard.vcf', + 'vcard_author.vcf', + 'vcard3.vcf', + 'vcard_grouped.vcf', + 'escape_semicolon.vcf' + ]; + + list.forEach(function(path) { + suite(path.replace('_', ' '), function() { + let input; + let expected; + + // fetch ical + setup(async function() { + input = await testSupport.load(root + path); + }); + + // fetch json + setup(async function() { + let data = await testSupport.load(root + path.replace(/vcf|ics$/, 'json')); + try { + expected = JSON.parse(data.trim()); + } catch { + throw new Error('expect json is invalid: \n\n' + data); + } + }); + + function jsonEqual(jsonActual, jsonExpected) { + assert.deepEqual( + jsonActual, + jsonExpected, + 'hint use: ' + + 'http://tlrobinson.net/projects/javascript-fun/jsondiff/\n\n' + + '\nexpected:\n\n' + + JSON.stringify(jsonActual, null, 2) + + '\n\n to equal:\n\n ' + + JSON.stringify(jsonExpected, null, 2) + '\n\n' + ); + } + + test('round-trip', function() { + let parsed = subject(input); + let ical = ICAL.stringify(parsed); + + // NOTE: this is not an absolute test that serialization + // works as our parser should be error tolerant and + // it is remotely possible that we consistently produce + // ICAL that only we can parse. + jsonEqual( + subject(ical), + expected + ); + }); + + test('compare', function() { + let actual = subject(input); + jsonEqual(actual, expected); + }); + }); + }); + }); + + suite('invalid ical', function() { + + test('invalid property', function() { + let ical = 'BEGIN:VCALENDAR\n'; + // no param or value token + ical += 'DTSTART\n'; + ical += 'DESCRIPTION:1\n'; + ical += 'END:VCALENDAR'; + + assert.throws(function() { + subject(ical); + }, /invalid line/); + }); + + test('invalid quoted params', function() { + let ical = 'BEGIN:VCALENDAR\n'; + ical += 'X-FOO;BAR="quoted\n'; + // an invalid newline inside quoted parameter + ical += 'params";FOO=baz:realvalue\n'; + ical += 'END:VCALENDAR'; + + assert.throws(function() { + subject(ical); + }, /invalid line/); + }); + + test('missing value with param delimiter', function() { + let ical = 'BEGIN:VCALENDAR\n' + + 'X-FOO;\n'; + assert.throws(function() { + subject(ical); + }, "Invalid parameters in"); + }); + + test('missing param name ', function() { + let ical = 'BEGIN:VCALENDAR\n' + + 'X-FOO;=\n'; + assert.throws(function() { + subject(ical); + }, "Empty parameter name in"); + }); + + test('missing param value', function() { + let ical = 'BEGIN:VCALENDAR\n' + + 'X-FOO;BAR=\n'; + assert.throws(function() { + subject(ical); + }, "Missing parameter value in"); + }); + + test('missing component end', function() { + let ical = 'BEGIN:VCALENDAR\n'; + ical += 'BEGIN:VEVENT\n'; + ical += 'BEGIN:VALARM\n'; + ical += 'DESCRIPTION: foo\n'; + ical += 'END:VALARM'; + // ended calendar before event + ical += 'END:VCALENDAR'; + + assert.throws(function() { + subject(ical); + }, /invalid/); + }); + + }); + + suite('#_parseParameters', function() { + test('with processed text', function() { + let input = ';FOO=x\\na'; + let expected = { + 'foo': 'x\na' + }; + + assert.deepEqual( + subject._parseParameters(input, 0, ICAL.design.defaultSet)[0], + expected + ); + }); + + test('with multiple vCard TYPE parameters', function() { + let input = ';TYPE=work;TYPE=voice'; + let expected = { + 'type': ['work', 'voice'] + }; + + assert.deepEqual( + subject._parseParameters(input, 0, ICAL.design.components.vcard)[0], + expected + ); + }); + + test('with multiple iCalendar MEMBER parameters', function() { + let input = ';MEMBER="urn:one","urn:two";MEMBER="urn:three"'; + let expected = { + 'member': ['urn:one', 'urn:two', 'urn:three'] + }; + + assert.deepEqual( + subject._parseParameters(input, 0, ICAL.design.components.vevent)[0], + expected + ); + }); + + test('with comma in singleValue parameter', function() { + let input = ';LABEL="A, B"'; + let expected = { + 'label': 'A, B' + }; + + assert.deepEqual( + subject._parseParameters(input, 0, ICAL.design.components.vcard)[0], + expected + ); + }); + + test('with comma in singleValue parameter after multiValue parameter', function() { + // TYPE allows multiple values, whereas LABEL doesn't. + let input = ';TYPE=home;LABEL="A, B"'; + let expected = { + 'type': 'home', + 'label': 'A, B' + }; + + assert.deepEqual( + subject._parseParameters(input, 0, ICAL.design.components.vcard)[0], + expected + ); + }); + + test('with quoted value', function() { + let input = ';FMTTYPE="text/html":Here is HTML with signs like =;'; + let expected = { + 'fmttype': 'text/html' + }; + + assert.deepEqual( + subject._parseParameters(input, 0, ICAL.design.components.vevent)[0], + expected + ); + }); + }); + + test('#_parseMultiValue', function() { + let values = 'woot\\, category,foo,bar,baz'; + let result = []; + assert.deepEqual( + subject._parseMultiValue(values, ',', 'text', result, null, ICAL.design.defaultSet), + ['woot, category', 'foo', 'bar', 'baz'] + ); + }); + + suite('#_parseValue', function() { + test('text', function() { + let value = 'start \\n next'; + let expected = 'start \n next'; + + assert.equal( + subject._parseValue(value, 'text', ICAL.design.defaultSet), + expected + ); + }); + }); + + suite('#_eachLine', function() { + + function unfold(input) { + let result = []; + + subject._eachLine(input, function(err, line) { + result.push(line); + }); + + return result; + } + + test('unfold single with \\r\\n', function() { + let input = 'foo\r\n bar'; + let expected = ['foobar']; + + assert.deepEqual(unfold(input), expected); + }); + + test('with \\n', function() { + let input = 'foo\nbar\n baz'; + let expected = [ + 'foo', + 'bar baz' + ]; + + assert.deepEqual(unfold(input), expected); + }); + }); + + suite('embedded timezones', function() { + let icsDataEmbeddedTimezones; + suiteSetup(async function() { + icsDataEmbeddedTimezones = await testSupport.loadSample('timezone_from_file.ics'); + }); + + test('used in event date', function() { + const parsed = ICAL.parse(icsDataEmbeddedTimezones); + const component = new ICAL.Component(parsed); + + const event = new ICAL.Event(component.getFirstSubcomponent('vevent')); + const startDate = event.startDate.toJSDate(); + const endDate = event.endDate.toJSDate(); + + assert.equal(startDate.getUTCDate(), 6); + assert.equal(startDate.getUTCHours(), 21); + assert.equal(startDate.getUTCMinutes(), 23); + + assert.equal(endDate.getUTCDate(), 6); + assert.equal(endDate.getUTCHours(), 22); + assert.equal(endDate.getUTCMinutes(), 23); + }); + }); +}); diff --git a/test/parser/base64.ics b/test/parser/base64.ics new file mode 100644 index 0000000..6f408e1 --- /dev/null +++ b/test/parser/base64.ics @@ -0,0 +1,5 @@ +BEGIN:VCALENDAR +ATTACH;FMTTYPE=text/plain;ENCODING=BASE64;VALUE=BINARY;X-BASE + 64-PARAM=UGFyYW1ldGVyCg=:WW91IHJlYWxseSBzcGVudCB0aGUgdGltZS + B0byBiYXNlNjQgZGVjb2RlIHRoaXM/Cg= +END:VCALENDAR diff --git a/test/parser/base64.json b/test/parser/base64.json new file mode 100644 index 0000000..8f5ca4d --- /dev/null +++ b/test/parser/base64.json @@ -0,0 +1,16 @@ +[ + "vcalendar", + [ + [ + "attach", + { + "fmttype": "text/plain", + "encoding": "BASE64", + "x-base64-param": "UGFyYW1ldGVyCg=" + }, + "binary", + "WW91IHJlYWxseSBzcGVudCB0aGUgdGltZSB0byBiYXNlNjQgZGVjb2RlIHRoaXM/Cg=" + ] + ], + [] +] diff --git a/test/parser/boolean.ics b/test/parser/boolean.ics new file mode 100644 index 0000000..bf11b68 --- /dev/null +++ b/test/parser/boolean.ics @@ -0,0 +1,5 @@ +BEGIN:VCALENDAR +X-TRUE;VALUE=BOOLEAN:TRUE +X-FALSE;VALUE=BOOLEAN:FALSE +X-MAYBE;VALUE=BOOLEAN:MAYBE +END:VCALENDAR diff --git a/test/parser/boolean.json b/test/parser/boolean.json new file mode 100644 index 0000000..c6f5311 --- /dev/null +++ b/test/parser/boolean.json @@ -0,0 +1,23 @@ +["vcalendar", + [ + [ + "x-true", + {}, + "boolean", + true + ], + [ + "x-false", + {}, + "boolean", + false + ], + [ + "x-maybe", + {}, + "boolean", + false + ] + ], + [] +] diff --git a/test/parser/component.ics b/test/parser/component.ics new file mode 100644 index 0000000..6a5250b --- /dev/null +++ b/test/parser/component.ics @@ -0,0 +1,12 @@ +BEGIN:VCALENDAR +BEGIN:VEVENT +SUMMARY:foo \\n + bar +BEGIN:VALARM +SUMMARY:escaped\, comma and\; semicolon\nnewline +END:VALARM +END:VEVENT +BEGIN:VEVENT +SUMMARY:another +END:VEVENT +END:VCALENDAR diff --git a/test/parser/component.json b/test/parser/component.json new file mode 100644 index 0000000..eaaa3bd --- /dev/null +++ b/test/parser/component.json @@ -0,0 +1,21 @@ +["vcalendar", + [], + [ + [ + "vevent", + [["summary", {}, "text", "foo \\nbar"]], + [ + [ + "valarm", + [["summary", {}, "text", "escaped, comma and; semicolon\nnewline"]], + [] + ] + ] + ], + [ + "vevent", + [["summary", {}, "text", "another"]], + [] + ] + ] +] diff --git a/test/parser/dates.ics b/test/parser/dates.ics new file mode 100644 index 0000000..2be64f0 --- /dev/null +++ b/test/parser/dates.ics @@ -0,0 +1,7 @@ +BEGIN:VCALENDAR +DTSTART:20120901T130000Z +DTEND;VALUE=DATE:20120901 +RDATE:20131210 +X-MYDATE;VALUE=DATE:20120901 +X-MYDATETIME;VALUE=DATE-TIME:20120901T130000 +END:VCALENDAR diff --git a/test/parser/dates.json b/test/parser/dates.json new file mode 100644 index 0000000..2f705c1 --- /dev/null +++ b/test/parser/dates.json @@ -0,0 +1,35 @@ +["vcalendar", + [ + [ + "dtstart", + {}, + "date-time", + "2012-09-01T13:00:00Z" + ], + [ + "dtend", + {}, + "date", + "2012-09-01" + ], + [ + "rdate", + {}, + "date", + "2013-12-10" + ], + [ + "x-mydate", + {}, + "date", + "2012-09-01" + ], + [ + "x-mydatetime", + {}, + "date-time", + "2012-09-01T13:00:00" + ] + ], + [] +] diff --git a/test/parser/escape_semicolon.json b/test/parser/escape_semicolon.json new file mode 100644 index 0000000..7940503 --- /dev/null +++ b/test/parser/escape_semicolon.json @@ -0,0 +1,26 @@ +[ + "vcard", + [ + [ "version", {}, "text", "3.0" ], + [ "prodid", {}, "text", "-//Sabre//Sabre VObject 4.1.6//EN" ], + [ "uid", {}, "text", "ad612c16-fe12-4ec5-abf6-49998ee5ab88" ], + [ "fn", {}, "text", "First Last NextCloud" ], + [ "adr", { "type": "HOME" }, "text", [ "PO", "Street 2", "Street", "City", "AL", "zip", "Germany" ] ], + [ "adr", { "type": "WORK" }, "text", [ "PO W", "Street 2 W", "Street W", "City Work", "AL work", "zip Work", "Germany work" ] ], + [ "email", { "type": "HOME" }, "text", "home@gmx.de" ], + [ "email", { "type": "WORK" }, "text", "work@gmx.de" ], + [ "tel", { "type": [ "HOME", "VOICE" ] }, "phone-number", "11111111" ], + [ "tel", { "type": "CELL" }, "phone-number", "205333" ], + [ "tel", { "type": ["WORK", "FAX" ] }, "phone-number", "205246;;,;" ], + [ "tel", { "type": ["WORK", "VOICE" ] }, "phone-number", "222222" ], + [ "org", {}, "text", "Firma" ], + [ "bday", {}, "date-time", "2019-02-10T00:00:33" ], + [ "nickname", {}, "text", "Hugo" ], + [ "x-abdate", {"group": "item1"}, "date-and-or-time", "20190220T000035" ], + [ "x-ablabel", {"group": "item1"}, "unknown", "_$!!$_" ], + [ "x-anniversary", {}, "date-and-or-time", "20190220T000035" ], + [ "categories", {}, "text", "Test-Kontakte" ], + [ "rev", {}, "date-time", "2019-10-08T17:05:14Z" ] + ], + [] + ] diff --git a/test/parser/escape_semicolon.vcf b/test/parser/escape_semicolon.vcf new file mode 100644 index 0000000..04d7189 --- /dev/null +++ b/test/parser/escape_semicolon.vcf @@ -0,0 +1,23 @@ +BEGIN:VCARD +VERSION:3.0 +PRODID:-//Sabre//Sabre VObject 4.1.6//EN +UID:ad612c16-fe12-4ec5-abf6-49998ee5ab88 +FN:First Last NextCloud +ADR;TYPE=HOME:PO;Street 2;Street;City;AL;zip;Germany +ADR;TYPE=WORK:PO W;Street 2 W;Street W;City Work;AL work;zip Work;Germany w + ork +EMAIL;TYPE=HOME:home@gmx.de +EMAIL;TYPE=WORK:work@gmx.de +TEL;TYPE="HOME,VOICE":11111111 +TEL;TYPE=CELL:205333 +TEL;TYPE="WORK,FAX":205246\;\;\,\; +TEL;TYPE="WORK,VOICE":222222 +ORG:Firma +BDAY:20190210T000033 +NICKNAME:Hugo +ITEM1.X-ABDATE;VALUE=DATE-AND-OR-TIME:20190220T000035 +ITEM1.X-ABLABEL:_$!!$_ +X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20190220T000035 +CATEGORIES:Test-Kontakte +REV:20191008T170514Z +END:VCARD diff --git a/test/parser/float.ics b/test/parser/float.ics new file mode 100644 index 0000000..b71f7cc --- /dev/null +++ b/test/parser/float.ics @@ -0,0 +1,5 @@ +BEGIN:VCALENDAR +X-FLOAT;VALUE=FLOAT:10.35 +X-INVALID-FLOAT;VALUE=FLOAT:my foo! +END:VCALENDAR + diff --git a/test/parser/float.json b/test/parser/float.json new file mode 100644 index 0000000..521a7fe --- /dev/null +++ b/test/parser/float.json @@ -0,0 +1,17 @@ +["vcalendar", + [ + [ + "x-float", + {}, + "float", + 10.35 + ], + [ + "x-invalid-float", + {}, + "float", + 0.0 + ] + ], + [] +] diff --git a/test/parser/grouped.ics b/test/parser/grouped.ics new file mode 100644 index 0000000..d8bc96a --- /dev/null +++ b/test/parser/grouped.ics @@ -0,0 +1,3 @@ +BEGIN:VCALENDAR +GROUP1.X-TEST;VALUE=TEXT:test! +END:VCALENDAR diff --git a/test/parser/grouped.json b/test/parser/grouped.json new file mode 100644 index 0000000..7713af2 --- /dev/null +++ b/test/parser/grouped.json @@ -0,0 +1,12 @@ +[ + "vcalendar", + [ + [ + "group1.x-test", + {}, + "text", + "test!" + ] + ], + [] +] diff --git a/test/parser/integer.ics b/test/parser/integer.ics new file mode 100644 index 0000000..e00242f --- /dev/null +++ b/test/parser/integer.ics @@ -0,0 +1,5 @@ +BEGIN:VCALENDAR +X-INTEGER;VALUE=INTEGER:105 +X-INVALID;VALUE=INTEGER:foobar +END:VCALENDAR + diff --git a/test/parser/integer.json b/test/parser/integer.json new file mode 100644 index 0000000..2875f67 --- /dev/null +++ b/test/parser/integer.json @@ -0,0 +1,17 @@ +["vcalendar", + [ + [ + "x-integer", + {}, + "integer", + 105 + ], + [ + "x-invalid", + {}, + "integer", + 0 + ] + ], + [] +] diff --git a/test/parser/multiple_root_components.ics b/test/parser/multiple_root_components.ics new file mode 100644 index 0000000..946c545 --- /dev/null +++ b/test/parser/multiple_root_components.ics @@ -0,0 +1,6 @@ +BEGIN:VCALENDAR +DTSTAMP:20140401T010101Z +END:VCALENDAR +BEGIN:VCALENDAR +DTSTAMP:20140401T020202Z +END:VCALENDAR diff --git a/test/parser/multiple_root_components.json b/test/parser/multiple_root_components.json new file mode 100644 index 0000000..d43d189 --- /dev/null +++ b/test/parser/multiple_root_components.json @@ -0,0 +1,10 @@ +[ + ["vcalendar", + [ ["dtstamp", {}, "date-time", "2014-04-01T01:01:01Z" ] ], + [] + ], + ["vcalendar", + [ ["dtstamp", {}, "date-time", "2014-04-01T02:02:02Z" ] ], + [] + ] +] diff --git a/test/parser/multivalue.ics b/test/parser/multivalue.ics new file mode 100644 index 0000000..c52b215 --- /dev/null +++ b/test/parser/multivalue.ics @@ -0,0 +1,7 @@ +BEGIN:VCALENDAR +CATEGORIES:foo,blue\, fish,woot +GEO:10.10;10.05 +REQUEST-STATUS:3.1;Invalid property value;DTSTART:96-Apr-01 +RDATE;VALUE=DATE:20121001,20121002,20121003 +EXDATE:20120901T130000,20120905T130000 +END:VCALENDAR diff --git a/test/parser/multivalue.json b/test/parser/multivalue.json new file mode 100644 index 0000000..91eba65 --- /dev/null +++ b/test/parser/multivalue.json @@ -0,0 +1,40 @@ +["vcalendar", + [ + [ + "categories", + {}, + "text", + "foo", + "blue, fish", + "woot" + ], + [ + "geo", + {}, + "float", + [10.10, 10.05] + ], + [ + "request-status", + {}, + "text", + ["3.1", "Invalid property value", "DTSTART:96-Apr-01"] + ], + [ + "rdate", + {}, + "date", + "2012-10-01", + "2012-10-02", + "2012-10-03" + ], + [ + "exdate", + {}, + "date-time", + "2012-09-01T13:00:00", + "2012-09-05T13:00:00" + ] + ], + [] +] diff --git a/test/parser/newline_junk.ics b/test/parser/newline_junk.ics new file mode 100644 index 0000000..bea9c1e --- /dev/null +++ b/test/parser/newline_junk.ics @@ -0,0 +1,6 @@ + + +BEGIN:VCALENDAR +END:VCALENDAR + + diff --git a/test/parser/newline_junk.json b/test/parser/newline_junk.json new file mode 100644 index 0000000..8d0649e --- /dev/null +++ b/test/parser/newline_junk.json @@ -0,0 +1 @@ +["vcalendar", [], []] diff --git a/test/parser/period.ics b/test/parser/period.ics new file mode 100644 index 0000000..9f3d553 --- /dev/null +++ b/test/parser/period.ics @@ -0,0 +1,5 @@ +BEGIN:VCALENDAR +RDATE;VALUE=PERIOD:19960403T020000Z/19960403T040000Z, + 19960404T010000/PT3H +END:VCALENDAR + diff --git a/test/parser/period.json b/test/parser/period.json new file mode 100644 index 0000000..787461b --- /dev/null +++ b/test/parser/period.json @@ -0,0 +1,12 @@ +["vcalendar", + [ + [ + "rdate", + {}, + "period", + ["1996-04-03T02:00:00Z", "1996-04-03T04:00:00Z"], + ["1996-04-04T01:00:00", "PT3H"] + ] + ], + [] +] diff --git a/test/parser/property_params.ics b/test/parser/property_params.ics new file mode 100644 index 0000000..e6c3697 --- /dev/null +++ b/test/parser/property_params.ics @@ -0,0 +1,20 @@ +BEGIN:VCALENDAR +ATTENDEE;DELEGATED-TO="mailto:foo7@bar","mailto:foo8@bar";CN="Foo, Bar":mai + lto:foo1@bar +ATTENDEE;DELEGATED-TO="mailto:foo7@bar","mailto:foo8@bar";CN="Foo; Bar":mai + lto:foo2@bar +ATTENDEE;CN="Foo, Bar":mailto:foo3@bar +ATTENDEE;CN="Foo; Bar":mailto:foo4@bar +ATTENDEE;DELEGATED-TO="mailto:foo7@bar";CN="Foo, Bar":mailto:foo5@bar +ATTENDEE;DELEGATED-TO="mailto:foo7@bar";CN="Foo; Bar":mailto:foo6@bar +ATTENDEE;CN="^^Ж 4 <>\'^'^n:":mailto:foo9@bar +ATTENDEE;ROLE="REQ-PARTICIPANT;foo";DELEGATED-FROM="mailto:bar@baz.com";PAR + TSTAT=ACCEPTED;RSVP=TRUE:mailto:foo@bar.com +ATTENDEE;CN=X\:mailto:x@example.org +ATTENDEE;CN="Y\":mailto:y@example.org +X-FOO;PARAM1=VAL1:FOO;BAR +X-FOO2;PARAM1=VAL1;PARAM2=VAL2:FOO;BAR +X-BAR;PARAM1="VAL1:FOO":BAZ;BAR +X-BAZ;PARAM1="VAL1:FOO";PARAM2=VAL2:BAZ;BAR +X-BAZ2;PARAM1=VAL1;PARAM2="VAL2:FOO":BAZ;BAR +END:VCALENDAR diff --git a/test/parser/property_params.json b/test/parser/property_params.json new file mode 100644 index 0000000..c5a6db4 --- /dev/null +++ b/test/parser/property_params.json @@ -0,0 +1,141 @@ +["vcalendar", + [ + [ + "attendee", + { + "delegated-to": [ + "mailto:foo7@bar", + "mailto:foo8@bar" + ], + "cn": "Foo, Bar" + }, + "cal-address", + "mailto:foo1@bar" + ], + [ + "attendee", + { + "delegated-to": [ + "mailto:foo7@bar", + "mailto:foo8@bar" + ], + "cn": "Foo; Bar" + }, + "cal-address", + "mailto:foo2@bar" + ], + [ + "attendee", + { + "cn": "Foo, Bar" + }, + "cal-address", + "mailto:foo3@bar" + ], + [ + "attendee", + { + "cn": "Foo; Bar" + }, + "cal-address", + "mailto:foo4@bar" + ], + [ + "attendee", + { + "delegated-to": "mailto:foo7@bar", + "cn": "Foo, Bar" + }, + "cal-address", + "mailto:foo5@bar" + ], + [ + "attendee", + { + "delegated-to": "mailto:foo7@bar", + "cn": "Foo; Bar" + }, + "cal-address", + "mailto:foo6@bar" + ], + [ + "attendee", + { + "cn": "^Ж 4 <>\\'\"\n:" + }, + "cal-address", + "mailto:foo9@bar" + ], + [ + "attendee", + { + "role": "REQ-PARTICIPANT;foo", + "delegated-from": "mailto:bar@baz.com", + "partstat": "ACCEPTED", + "rsvp": "TRUE" + }, + "cal-address", + "mailto:foo@bar.com" + ], + [ + "attendee", + { + "cn": "X\\" + }, + "cal-address", + "mailto:x@example.org" + ], + [ + "attendee", + { + "cn": "Y\\" + }, + "cal-address", + "mailto:y@example.org" + ], + [ + "x-foo", + { + "param1": "VAL1" + }, + "unknown", + "FOO;BAR" + ], + [ + "x-foo2", + { + "param1": "VAL1", + "param2": "VAL2" + }, + "unknown", + "FOO;BAR" + ], + [ + "x-bar", + { + "param1": "VAL1:FOO" + }, + "unknown", + "BAZ;BAR" + ], + [ + "x-baz", + { + "param1": "VAL1:FOO", + "param2": "VAL2" + }, + "unknown", + "BAZ;BAR" + ], + [ + "x-baz2", + { + "param1": "VAL1", + "param2": "VAL2:FOO" + }, + "unknown", + "BAZ;BAR" + ] + ], + [] +] diff --git a/test/parser/quoted_params.ics b/test/parser/quoted_params.ics new file mode 100644 index 0000000..7f0052f --- /dev/null +++ b/test/parser/quoted_params.ics @@ -0,0 +1,4 @@ +BEGIN:VCALENDAR +X-FOO;BAR=";hidden=value";FOO=baz:realvalue + yep +END:VCALENDAR diff --git a/test/parser/quoted_params.json b/test/parser/quoted_params.json new file mode 100644 index 0000000..40058c1 --- /dev/null +++ b/test/parser/quoted_params.json @@ -0,0 +1,14 @@ +["vcalendar", + [ + [ + "x-foo", + { + "bar": ";hidden=value", + "foo": "baz" + }, + "unknown", + "realvalue yep" + ] + ], + [] +] diff --git a/test/parser/recur.ics b/test/parser/recur.ics new file mode 100644 index 0000000..9b397ac --- /dev/null +++ b/test/parser/recur.ics @@ -0,0 +1,7 @@ +BEGIN:VCALENDAR +RRULE:FREQ=MONTHLY;BYMONTH=1,3 +RRULE:FREQ=MONTHLY;BYMONTH=2 +RRULE:FREQ=DAILY;UNTIL=20121011 +RRULE:FREQ=DAILY;UNTIL=20121011T121314 +RRULE:FREQ=DAILY;UNTIL=20121011T121314Z +END:VCALENDAR diff --git a/test/parser/recur.json b/test/parser/recur.json new file mode 100644 index 0000000..9f02a29 --- /dev/null +++ b/test/parser/recur.json @@ -0,0 +1,35 @@ +["vcalendar", + [ + [ + "rrule", + {}, + "recur", + { "freq": "MONTHLY", "bymonth": [1,3] } + ], + [ + "rrule", + {}, + "recur", + { "freq": "MONTHLY", "bymonth": 2 } + ], + [ + "rrule", + {}, + "recur", + { "freq": "DAILY", "until": "2012-10-11" } + ], + [ + "rrule", + {}, + "recur", + { "freq": "DAILY", "until": "2012-10-11T12:13:14" } + ], + [ + "rrule", + {}, + "recur", + { "freq": "DAILY", "until": "2012-10-11T12:13:14Z" } + ] + ], + [] +] diff --git a/test/parser/rfc.ics b/test/parser/rfc.ics new file mode 100644 index 0000000..3861fd4 --- /dev/null +++ b/test/parser/rfc.ics @@ -0,0 +1,11 @@ +BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:-//Example Inc.//Example Calendar//EN +VERSION:2.0 +BEGIN:VEVENT +DTSTAMP:20080205T191224Z +DTSTART;VALUE=DATE:20081006 +SUMMARY:Planning meeting +UID:4088E990AD89CB3DBB484909 +END:VEVENT +END:VCALENDAR diff --git a/test/parser/rfc.json b/test/parser/rfc.json new file mode 100644 index 0000000..8505d59 --- /dev/null +++ b/test/parser/rfc.json @@ -0,0 +1,18 @@ +["vcalendar", + [ + ["calscale", {}, "text", "GREGORIAN"], + ["prodid", {}, "text", "-//Example Inc.//Example Calendar//EN"], + ["version", {}, "text", "2.0"] + ], + [ + ["vevent", + [ + ["dtstamp", {}, "date-time", "2008-02-05T19:12:24Z"], + ["dtstart", {}, "date", "2008-10-06"], + ["summary", {}, "text", "Planning meeting"], + ["uid", {}, "text", "4088E990AD89CB3DBB484909"] + ], + [] + ] + ] +] diff --git a/test/parser/single_empty_vcalendar.ics b/test/parser/single_empty_vcalendar.ics new file mode 100644 index 0000000..afa30f4 --- /dev/null +++ b/test/parser/single_empty_vcalendar.ics @@ -0,0 +1,2 @@ +BEGIN:VCALENDAR +END:VCALENDAR diff --git a/test/parser/single_empty_vcalendar.json b/test/parser/single_empty_vcalendar.json new file mode 100644 index 0000000..8d0649e --- /dev/null +++ b/test/parser/single_empty_vcalendar.json @@ -0,0 +1 @@ +["vcalendar", [], []] diff --git a/test/parser/time.ics b/test/parser/time.ics new file mode 100644 index 0000000..2ce1ae5 --- /dev/null +++ b/test/parser/time.ics @@ -0,0 +1,5 @@ +BEGIN:VCALENDAR +X-TIME;VALUE=TIME:230000 +X-TIMEZ;VALUE=TIME:230000Z +END:VCALENDAR + diff --git a/test/parser/time.json b/test/parser/time.json new file mode 100644 index 0000000..6f0eba3 --- /dev/null +++ b/test/parser/time.json @@ -0,0 +1,18 @@ +["vcalendar", + [ + [ + "x-time", + {}, + "time", + "23:00:00" + ], + [ + "x-timez", + {}, + "time", + "23:00:00Z" + ] + + ], + [] +] diff --git a/test/parser/tzid_with_gmt.ics b/test/parser/tzid_with_gmt.ics new file mode 100644 index 0000000..e40ba57 --- /dev/null +++ b/test/parser/tzid_with_gmt.ics @@ -0,0 +1,3 @@ +BEGIN:VCALENDAR +DTSTART;TZID="(GMT +01:00)":20111028T160000 +END:VCALENDAR diff --git a/test/parser/tzid_with_gmt.json b/test/parser/tzid_with_gmt.json new file mode 100644 index 0000000..0726ef4 --- /dev/null +++ b/test/parser/tzid_with_gmt.json @@ -0,0 +1,11 @@ +["vcalendar", + [ + [ + "dtstart", + { "tzid": "(GMT +01:00)" }, + "date-time", + "2011-10-28T16:00:00" + ] + ], + [] +] diff --git a/test/parser/unfold_properties.ics b/test/parser/unfold_properties.ics new file mode 100644 index 0000000..9416aab --- /dev/null +++ b/test/parser/unfold_properties.ics @@ -0,0 +1,5 @@ +BEGIN:VCALENDAR +ATTENDEE;CN=Person;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=TRU + E:mailto:person@foo.com +END:VCALENDAR + diff --git a/test/parser/unfold_properties.json b/test/parser/unfold_properties.json new file mode 100644 index 0000000..521db0a --- /dev/null +++ b/test/parser/unfold_properties.json @@ -0,0 +1,16 @@ +["vcalendar", + [ + [ + "attendee", + { + "cn": "Person", + "role": "REQ-PARTICIPANT", + "partstat": "ACCEPTED", + "rsvp": "TRUE" + }, + "cal-address", + "mailto:person@foo.com" + ] + ], + [] +] diff --git a/test/parser/utc_offset.ics b/test/parser/utc_offset.ics new file mode 100644 index 0000000..d9db4e5 --- /dev/null +++ b/test/parser/utc_offset.ics @@ -0,0 +1,6 @@ +BEGIN:VCALENDAR +X-OFFSET;VALUE=UTC-OFFSET:-0500 +X-OFFSET-SECONDS;VALUE=UTC-OFFSET:+055001 +END:VCALENDAR + + diff --git a/test/parser/utc_offset.json b/test/parser/utc_offset.json new file mode 100644 index 0000000..1b8aea8 --- /dev/null +++ b/test/parser/utc_offset.json @@ -0,0 +1,7 @@ +["vcalendar", + [ + ["x-offset", {}, "utc-offset", "-05:00"], + ["x-offset-seconds", {}, "utc-offset", "+05:50:01"] + ], + [] +] diff --git a/test/parser/values.ics b/test/parser/values.ics new file mode 100644 index 0000000..9384453 --- /dev/null +++ b/test/parser/values.ics @@ -0,0 +1,3 @@ +BEGIN:VCALENDAR +LOCATION: +END:VCALENDAR diff --git a/test/parser/values.json b/test/parser/values.json new file mode 100644 index 0000000..d4abb00 --- /dev/null +++ b/test/parser/values.json @@ -0,0 +1,11 @@ +["vcalendar", + [ + [ + "location", + {}, + "text", + "" + ] + ], + [] +] diff --git a/test/parser/vcard.json b/test/parser/vcard.json new file mode 100644 index 0000000..01b2771 --- /dev/null +++ b/test/parser/vcard.json @@ -0,0 +1,38 @@ +[ + "vcard", + [ + [ "version", {}, "text", "4.0" ], + [ "adr", { "type": "work" }, "text", [ "pobox", "apt", "street", "city", "state", "zipcode", "country" ] ], + [ "anniversary", {}, "date-and-or-time", "1996-04-15" ], + [ "bday", {}, "date-and-or-time", "--02-03" ], + [ "caladruri", {}, "uri", "http://example.com/calendar/jdoe" ], + [ "caluri", { "mediatype": "text/calendar" }, "uri", "ftp://ftp.example.com/calA.ics" ], + [ "clientpidmap", {}, "text", [ "1", "urn:uuid:3df403f4-5924-4bb7-b077-3c711d9eb34b" ] ], + [ "email", { "type": "work" }, "text", "jqpublic@xyz.example.com" ], + [ "fburl", { "mediatype": "text/calendar" }, "uri", "ftp://example.com/busy/project-a.ifb" ], + [ "fn", {}, "text", "J. Doe" ], + [ "gender", {}, "text", [ "M", "Fellow" ] ], + [ "geo", {}, "uri", "geo:37.386013,-122.082932" ], + [ "impp", { "pref": "1" }, "uri", "xmpp:alice@example.com" ], + [ "key", {}, "uri", "http://www.example.com/keys/jdoe.cer" ], + [ "kind", {}, "text", "individual" ], + [ "lang", { "pref": "1" }, "language-tag", "fr" ], + [ "logo", {}, "uri", "http://www.example.com/pub/logos/abccorp.jpg" ], + [ "member", {}, "uri", "urn:uuid:03a0e51f-d1aa-4385-8a53-e29025acd8af" ], + [ "n", {}, "text", [ "Stevenson", "John", ["Philip", "Paul"], "Dr.", [ "Jr.", "M.D.", "A.C.P." ] ] ], + [ "nickname", { "type": "work" }, "text", "Boss" ], + [ "note", {}, "text", "This fax number is operational 0800 to 1715 EST, Mon-Fri" ], + [ "org", {}, "text", [ "ABC, Inc.", "North American Division", "Marketing" ] ], + [ "photo", {}, "uri", "http://www.example.com/pub/photos/jqpublic.gif" ], + [ "related", { "type": "friend" }, "uri", "urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6" ], + [ "rev", {}, "timestamp", "1995-10-31T22:27:10Z" ], + [ "role", {}, "text", "Project Leader" ], + [ "sound", {}, "uri", "CID:JOHNQPUBLIC.part8.19960229T080000.xyzMail@example.com" ], + [ "source", {}, "uri", "ldap://ldap.example.com/cn=Babs%20Jensen,%20o=Babsco,%20c=US" ], + [ "tel", { "type": "home" }, "uri", "tel:+33-01-23-45-67" ], + [ "title", {}, "text", "Research Scientist" ], + [ "tz", {}, "utc-offset", "-05:00" ], + [ "xml", {}, "text", "" ] + ], + [] +] diff --git a/test/parser/vcard.vcf b/test/parser/vcard.vcf new file mode 100644 index 0000000..bab8344 --- /dev/null +++ b/test/parser/vcard.vcf @@ -0,0 +1,34 @@ +BEGIN:VCARD +VERSION:4.0 +ADR;TYPE=work:pobox;apt;street;city;state;zipcode;country +ANNIVERSARY:19960415 +BDAY:--0203 +CALADRURI:http://example.com/calendar/jdoe +CALURI;MEDIATYPE=text/calendar:ftp://ftp.example.com/calA.ics +CLIENTPIDMAP:1;urn:uuid:3df403f4-5924-4bb7-b077-3c711d9eb34b +EMAIL;TYPE=work:jqpublic@xyz.example.com +FBURL;MEDIATYPE=text/calendar:ftp://example.com/busy/project-a.ifb +FN:J. Doe +GENDER:M;Fellow +GEO:geo:37.386013\,-122.082932 +IMPP;PREF=1:xmpp:alice@example.com +KEY:http://www.example.com/keys/jdoe.cer +KIND:individual +LANG;PREF=1:fr +LOGO:http://www.example.com/pub/logos/abccorp.jpg +MEMBER:urn:uuid:03a0e51f-d1aa-4385-8a53-e29025acd8af +N:Stevenson;John;Philip,Paul;Dr.;Jr.,M.D.,A.C.P. +NICKNAME;TYPE=work:Boss +NOTE:This fax number is operational 0800 to 1715 EST\, Mon-Fri +ORG:ABC\, Inc.;North American Division;Marketing +PHOTO:http://www.example.com/pub/photos/jqpublic.gif +RELATED;TYPE=friend:urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6 +REV:19951031T222710Z +ROLE:Project Leader +SOUND:CID:JOHNQPUBLIC.part8.19960229T080000.xyzMail@example.com +SOURCE:ldap://ldap.example.com/cn=Babs%20Jensen\,%20o=Babsco\,%20c=US +TEL;VALUE=uri;TYPE=home:tel:+33-01-23-45-67 +TITLE:Research Scientist +TZ;VALUE=utc-offset:-0500 +XML: +END:VCARD diff --git a/test/parser/vcard3.json b/test/parser/vcard3.json new file mode 100644 index 0000000..ab2149b --- /dev/null +++ b/test/parser/vcard3.json @@ -0,0 +1,47 @@ +[ + "vcard", + [ + [ "version", {}, "text", "3.0" ], + [ "fn", {}, "text", "Mr. John Q. Public, Esq." ], + [ "n", {}, "text", [ "Public", "John", "Quinlan", "Mr.", "Esq." ] ], + [ "n", {}, "text", [ "Stevenson", "John", [ "Philip", "Paul" ], "Dr.", [ "Jr.", "M.D.", "A.C.P." ] ] ], + [ "nickname", {}, "text", "Robbie" ], + [ "nickname", {}, "text", "Jim", "Jimmie" ], + [ "photo", {}, "uri", "http://www.abc.com/pub/photos/jqpublic.gif" ], + [ "photo", { "encoding": "b", "type": "JPEG" }, "binary", "SGVsbG8sIHRoaXMgaXMgbm90IGEgcmVhbCBpbWFnZSBqdXN0IGEgdGVzdC4=" ], + [ "bday", {}, "date", "1996-04-15" ], + [ "bday", {}, "date-time", "1953-10-15T23:10:00Z" ], + [ "bday", {}, "date-time", "1987-09-27T08:30:00-06:00" ], + [ "adr", { "type": [ "dom", "home", "postal", "parcel" ] }, "text", ["", "", "123 Main Street", "Any Town", "CA", "91921-1234" ] ], + [ "label", { "type": [ "dom", "home", "postal", "parcel" ] }, "text", "Mr.John Q. Public, Esq.\nMail Drop: TNE QB\n123 Main Street\nAny Town, CA 91921-1234\nU.S.A." ], + [ "tel", { "type": [ "work", "voice", "pref", "msg" ] }, "phone-number", "+1-213-555-1234" ], + [ "email", { "type": "internet" }, "text", "jqpublic@xyz.dom1.com" ], + [ "email", { "type": "internet" }, "text", "jdoe@isp.net" ], + [ "email", { "type": [ "internet", "pref" ] }, "text", "jane_doe@abc.com" ], + [ "mailer", {}, "text", "PigeonMail 2.1" ], + [ "tz", {}, "utc-offset", "-05:00" ], + [ "tz", {}, "text", "-05:00; EST; Raleigh/North America;This example has a single value, not a structure text value." ], + [ "geo", {}, "float", [37.386013, -122.082932 ] ], + [ "title", {}, "text", "Director, Research and Development" ], + [ "role", {}, "text", "Programmer" ], + [ "logo", {}, "uri", "http://www.abc.com/pub/logos/abccorp.jpg" ], + [ "logo", { "encoding": "b", "type": "JPEG" }, "binary", "SGVsbG8sIHRoaXMgaXMgbm90IGEgcmVhbCBpbWFnZSBqdXN0IGEgdGVzdC4=" ], + [ "agent", {}, "uri", "CID:JQPUBLIC.part3.960129T083020.xyzMail@host3.com" ], + [ "agent", {}, "vcard", "BEGIN:VCARD\nFN:Susan Thomas\nTEL:+1-919-555-1234\nEMAIL;INTERNET:sthomas@host.com\nEND:VCARD\n" ], + [ "org", {}, "text", ["ABC, Inc.", "North American Division", "Marketing" ] ], + [ "categories", {}, "text", "TRAVEL AGENT" ], + [ "categories", {}, "text", "INTERNET", "IETF", "INDUSTRY", "INFORMATION TECHNOLOGY" ], + [ "note", {}, "text", "This fax number is operational 0800 to 1715 EST, Mon-Fri." ], + [ "prodid", {}, "text", "-//ONLINE DIRECTORY//NONSGML Version 1//EN" ], + [ "rev", {}, "date-time", "1995-10-31T22:27:10Z" ], + [ "rev", {}, "date", "1997-11-15" ], + [ "sort-string", {}, "text", "Harten" ], + [ "sound", { "type": "BASIC" }, "uri", "CID:JOHNQPUBLIC.part8.19960229T080000.xyzMail@host1.com" ], + [ "sound", { "type": "BASIC", "encoding": "b" }, "binary", "VGhlcmUgaXMgbm8gc291bmQgaW4gc3BhY2U=" ], + [ "class", {}, "text", "PUBLIC" ], + [ "class", {}, "text", "PRIVATE" ], + [ "class", {}, "text", "CONFIDENTIAL" ], + [ "key", { "encoding": "b" }, "binary", "Tm90IHRoZSBrZXkgeW91IGFyZSBsb29raW5nIGZvcg==" ] + ], + [] +] diff --git a/test/parser/vcard3.vcf b/test/parser/vcard3.vcf new file mode 100644 index 0000000..47ecabe --- /dev/null +++ b/test/parser/vcard3.vcf @@ -0,0 +1,54 @@ +BEGIN:VCARD +VERSION:3.0 +FN:Mr. John Q. Public\, Esq. +N:Public;John;Quinlan;Mr.;Esq. +N:Stevenson;John;Philip,Paul;Dr.;Jr.,M.D.,A.C.P. +NICKNAME:Robbie +NICKNAME:Jim,Jimmie +PHOTO;VALUE=uri:http://www.abc.com/pub/photos + /jqpublic.gif +PHOTO;ENCODING=b;TYPE=JPEG:SGVsbG8sIHRoaXMgaXMgbm90IGEgcmVhbCBp + bWFnZSBqdXN0IGEgdGVzdC4= +BDAY:1996-04-15 +BDAY:1953-10-15T23:10:00Z +BDAY:1987-09-27T08:30:00-06:00 +ADR;TYPE=dom,home,postal,parcel:;;123 Main + Street;Any Town;CA;91921-1234 +LABEL;TYPE=dom,home,postal,parcel:Mr.John Q. Public\, Esq.\n + Mail Drop: TNE QB\n123 Main Street\nAny Town\, CA 91921-1234 + \nU.S.A. +TEL;TYPE=work,voice,pref,msg:+1-213-555-1234 +EMAIL;TYPE=internet:jqpublic@xyz.dom1.com +EMAIL;TYPE=internet:jdoe@isp.net +EMAIL;TYPE=internet,pref:jane_doe@abc.com +MAILER:PigeonMail 2.1 +TZ:-05:00 +TZ;VALUE=text:-05:00\; EST\; Raleigh/North America + \;This example has a single value\, not a structure text value. +GEO:37.386013;-122.082932 +TITLE:Director\, Research and Development +ROLE:Programmer +LOGO;VALUE=uri:http://www.abc.com/pub/logos/abccorp.jpg +LOGO;ENCODING=b;TYPE=JPEG:SGVsbG8sIHRoaXMgaXMgbm90IGEgcmVhbCBp + bWFnZSBqdXN0IGEgdGVzdC4= +AGENT;VALUE=uri: + CID:JQPUBLIC.part3.960129T083020.xyzMail@host3.com +AGENT:BEGIN:VCARD\nFN:Susan Thomas\nTEL:+1-919-555- + 1234\nEMAIL\;INTERNET:sthomas@host.com\nEND:VCARD\n +ORG:ABC\, Inc.;North American Division;Marketing +CATEGORIES:TRAVEL AGENT +CATEGORIES:INTERNET,IETF,INDUSTRY,INFORMATION TECHNOLOGY +NOTE:This fax number is operational 0800 to 1715 + EST\, Mon-Fri. +PRODID:-//ONLINE DIRECTORY//NONSGML Version 1//EN +REV:1995-10-31T22:27:10Z +REV:1997-11-15 +SORT-STRING:Harten +SOUND;TYPE=BASIC;VALUE=uri:CID:JOHNQPUBLIC.part8. + 19960229T080000.xyzMail@host1.com +SOUND;TYPE=BASIC;ENCODING=b:VGhlcmUgaXMgbm8gc291bmQgaW4gc3BhY2U= +CLASS:PUBLIC +CLASS:PRIVATE +CLASS:CONFIDENTIAL +KEY;ENCODING=b:Tm90IHRoZSBrZXkgeW91IGFyZSBsb29raW5nIGZvcg== +END:VCARD diff --git a/test/parser/vcard_author.json b/test/parser/vcard_author.json new file mode 100644 index 0000000..9b76672 --- /dev/null +++ b/test/parser/vcard_author.json @@ -0,0 +1,58 @@ +["vcard", + [ + ["version", {}, "text", "4.0"], + ["fn", {}, "text", "Simon Perreault"], + ["n", + {}, + "text", + ["Perreault", "Simon", "", "", ["ing. jr", "M.Sc."]] + ], + ["bday", {}, "date-and-or-time", "--02-03"], + ["anniversary", + {}, + "date-and-or-time", + "2009-08-08T14:30-05:00" + ], + ["gender", {}, "text", "M"], + ["lang", { "pref": "1" }, "language-tag", "fr"], + ["lang", { "pref": "2" }, "language-tag", "en"], + ["org", { "type": "work" }, "text", "Viagenie"], + ["adr", + { "type": "work" }, + "text", + [ + "", + "Suite D2-630", + "2875 Laurier", + "Quebec", + "QC", + "G1V 2M2", + "Canada" + ] + ], + ["tel", + { "type": ["work", "voice"], "pref": "1" }, + "uri", + "tel:+1-418-656-9254;ext=102" + ], + ["tel", + { "type": ["work", "cell", "voice", "video", "text"] }, + "uri", + "tel:+1-418-262-6501" + ], + ["email", + { "type": "work" }, + "text", + "simon.perreault@viagenie.ca" + ], + ["geo", { "type": "work" }, "uri", "geo:46.772673,-71.282945"], + ["key", + { "type": "work" }, + "uri", + "http://www.viagenie.ca/simon.perreault/simon.asc" + ], + ["tz", {}, "utc-offset", "-05:00"], + ["url", { "type": "home" }, "uri", "http://nomis80.org"] + ], + [] +] diff --git a/test/parser/vcard_author.vcf b/test/parser/vcard_author.vcf new file mode 100644 index 0000000..763a6de --- /dev/null +++ b/test/parser/vcard_author.vcf @@ -0,0 +1,21 @@ +BEGIN:VCARD +VERSION:4.0 +FN:Simon Perreault +N:Perreault;Simon;;;ing. jr,M.Sc. +BDAY:--0203 +ANNIVERSARY:20090808T1430-0500 +GENDER:M +LANG;PREF=1:fr +LANG;PREF=2:en +ORG;TYPE=work:Viagenie +ADR;TYPE=work:;Suite D2-630;2875 Laurier; + Quebec;QC;G1V 2M2;Canada +TEL;VALUE=uri;TYPE="work,voice";PREF=1:tel:+1-418-656-9254;ext=102 +TEL;VALUE=uri;TYPE="work,cell,voice,video,text":tel:+1-418-262-6501 +EMAIL;TYPE=work:simon.perreault@viagenie.ca +GEO;TYPE=work:geo:46.772673\,-71.282945 +KEY;TYPE=work;VALUE=uri: + http://www.viagenie.ca/simon.perreault/simon.asc +TZ;VALUE=utc-offset:-0500 +URL;TYPE=home:http://nomis80.org +END:VCARD diff --git a/test/parser/vcard_grouped.json b/test/parser/vcard_grouped.json new file mode 100644 index 0000000..6a4422d --- /dev/null +++ b/test/parser/vcard_grouped.json @@ -0,0 +1,9 @@ +[ + "vcard", + [ + [ "version", {}, "text", "4.0" ], + [ "adr", { "type": "work" }, "text", [ "pobox", "apt", "street", "city", "state", "zipcode", "country" ] ], + [ "adr", { "type": "home", "group": "item1" }, "text", [ "pobox1", "apt1", "street1", "city1", "state1", "zipcode1", "country1" ] ] + ], + [] +] diff --git a/test/parser/vcard_grouped.vcf b/test/parser/vcard_grouped.vcf new file mode 100644 index 0000000..04514c2 --- /dev/null +++ b/test/parser/vcard_grouped.vcf @@ -0,0 +1,5 @@ +BEGIN:VCARD +VERSION:4.0 +ADR;TYPE=work:pobox;apt;street;city;state;zipcode;country +ITEM1.ADR;TYPE=home:pobox1;apt1;street1;city1;state1;zipcode1;country1 +END:VCARD diff --git a/test/performance/iterator_test.js b/test/performance/iterator_test.js new file mode 100644 index 0000000..8f0fb4b --- /dev/null +++ b/test/performance/iterator_test.js @@ -0,0 +1,28 @@ +suite('iterator', function() { + + let icsData; + + suiteSetup(async function() { + icsData = await testSupport.loadSample('parserv2.ics'); + }); + + let parsed; + let comp; + let tz; + let std; + let rrule; + + suiteSetup(function() { + parsed = ICAL.parse(icsData); + comp = new ICAL.Component(parsed); + tz = comp.getFirstSubcomponent('vtimezone'); + std = tz.getFirstSubcomponent('standard'); + rrule = std.getFirstPropertyValue('rrule'); + }); + + perfTest('timezone iterator & first iteration', function() { + let iterator = rrule.iterator(std.getFirstPropertyValue('dtstart')); + iterator.next(); + }); + +}); diff --git a/test/performance/parse_test.js b/test/performance/parse_test.js new file mode 100644 index 0000000..49fae21 --- /dev/null +++ b/test/performance/parse_test.js @@ -0,0 +1,17 @@ +suite('ICAL parse/stringify', function() { + + let icsData; + let parsed; + suiteSetup(async function() { + icsData = await testSupport.loadSample('parserv2.ics'); + parsed = ICAL.parse(icsData); + }); + + perfTest('#parse', function() { + ICAL.parse(icsData); + }); + + perfTest('#stringify', function() { + ICAL.stringify(parsed); + }); +}); diff --git a/test/performance/rrule_test.js b/test/performance/rrule_test.js new file mode 100644 index 0000000..dd8d4e7 --- /dev/null +++ b/test/performance/rrule_test.js @@ -0,0 +1,58 @@ +suite('rrule', function() { + + let start; + let occurrences; + + suiteSetup(function() { + start = ICAL.Time.fromString("2015-01-01T12:00:00"); + occurrences = 50; + }); + + + // These are common rules that can be created in the UI of various clients. + // At the moment we will just check getting 50 occurrences with INTERVAL=1. + // Checking COUNT, UNTIL and a higher INTERVAL could be applied to any rule, + // which would be quite a lot of combinations. Therefore those rules are just + // checked once. + [ + // COUNT, UNTIL and INTERVAL + "FREQ=DAILY;COUNT=50", + "FREQ=DAILY;UNTIL=20150220T120000", + "FREQ=DAILY;INTERVAL=7", + + // Lightning rules + "FREQ=DAILY", + + "FREQ=WEEKLY", + "FREQ=WEEKLY;BYDAY=MO,WE,FR", + + "FREQ=MONTHLY", + "FREQ=MONTHLY;BYMONTHDAY=1,15,31", + "FREQ=MONTHLY;BYMONTHDAY=-1", + "FREQ=MONTHLY;BYDAY=FR", + "FREQ=MONTHLY;BYDAY=-1SU", + "FREQ=MONTHLY;BYDAY=3MO", + + "FREQ=YEARLY", + "FREQ=YEARLY;BYMONTHDAY=23;BYMONTH=11", + "FREQ=YEARLY;BYDAY=4TH;BYMONTH=11", + + "FREQ=YEARLY;BYDAY=MO,TU,WE,TH,FR,SA,SU;BYMONTH=11", + "FREQ=YEARLY;BYDAY=-1SU;BYMONTH=11", + "FREQ=YEARLY;BYDAY=4TH;BYMONTH=11", + + // Apple iCal rules + "FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=3", + "FREQ=MONTHLY;BYDAY=SA,SU;BYMONTH=11;BYSETPOS=-1" + + ].forEach(function(rulestring) { + perfTest(rulestring, function() { + let rrule = ICAL.Recur.fromString(rulestring); + let iter = rrule.iterator(start); + for (let i = 0; i < occurrences; i++) { + iter.next(); + } + }); + }); + +}); diff --git a/test/performance/time_test.js b/test/performance/time_test.js new file mode 100644 index 0000000..a1aff4e --- /dev/null +++ b/test/performance/time_test.js @@ -0,0 +1,84 @@ +suite('ICAL.Time', function() { + + perfTest('subtract date', function() { + let time = new ICAL.Time({ + year: 2012, + month: 1, + day: 1, + hour: 10, + minute: 3 + }); + + let time2 = new ICAL.Time({ + year: 2012, + month: 10, + day: 1, + hour: 1, + minute: 55 + }); + + time.subtractDate(time2); + }); + + let dur = new ICAL.Duration({ + days: 3, + hour: 3, + minutes: 3 + }); + + perfTest('add duration', function() { + let time = new ICAL.Time({ + year: 2012, + month: 1, + day: 32, + seconds: 1 + }); + + time.addDuration(dur); + + // to trigger normalization + time.year; // eslint-disable-line no-unused-expressions + }); + + perfTest('create and clone time', function() { + let time = new ICAL.Time({ + year: 2012, + month: 1, + day: 32, + seconds: 1 + }); + + if (time.day !== 1) { + throw new Error('test sanity fails for .day'); + } + + if (time.month !== 2) { + throw new Error('test sanity fails for .month'); + } + + time.clone(); + }); + + let _time = new ICAL.Time({ + year: 2012, + month: 1, + day: 32, + seconds: 1 + }); + + perfTest('toUnixTime', function() { + _time.toUnixTime(); + }); + + perfTest('fromUnixTime', function() { + _time.fromUnixTime(1234567890); + }); + + perfTest('dayOfWeek', function() { + _time.dayOfWeek(); + }); + + perfTest('weekNumber', function() { + _time.weekNumber(); + }); +}); diff --git a/test/period_test.js b/test/period_test.js new file mode 100644 index 0000000..aac008b --- /dev/null +++ b/test/period_test.js @@ -0,0 +1,315 @@ +suite('ical/period', function() { + + let start, end, duration; + + setup(function() { + start = ICAL.Time.fromString("1970-01-02T03:04:05Z"); + end = ICAL.Time.fromString("1970-01-02T03:04:05Z"); + duration = ICAL.Duration.fromString("PT3H2M1S"); + }); + + suite('#fromString', function() { + function verify(string, icalstring, data) { + test('parse: "' + string + '"', function() { + let subject = ICAL.Period.fromString(string); + + assert.equal(subject.toICALString(), icalstring); + assert.equal(subject.toString(), string); + + if ('start' in data) { + assert.instanceOf(subject.start, ICAL.Time); + assert.hasProperties( + subject.start, + data.start, + 'start property' + ); + } + + if ('end' in data) { + if (data.end) { + assert.instanceOf(subject.end, ICAL.Time); + assert.hasProperties( + subject.end, + data.end, + 'end property' + ); + } else { + assert.isNull(subject.end); + } + } + + if ('duration' in data) { + if (data.duration) { + assert.instanceOf(subject.duration, ICAL.Duration); + assert.hasProperties( + subject.duration, + data.duration, + 'duration property' + ); + } else { + assert.isNull(subject.duration); + } + } + + if ('calculatedDuration' in data) { + let dur = subject.getDuration(); + + if ('duration' in data && data.duration) { + assert.hasProperties(dur, data.duration, 'duration matches calculated'); + } + assert.hasProperties(dur, data.calculatedDuration); + } + if ('calculatedEnd' in data) { + let subjectEnd = subject.getEnd(); + + if ('end' in data && data.end) { + assert.hasProperties(subjectEnd, data.end, 'duration matches calculated'); + } + assert.hasProperties(subjectEnd, data.calculatedEnd); + } + }); + } + + function verifyFail(testname, string, errorParam) { + test('invalid input "' + string + '"', function() { + assert.throws(function() { + ICAL.Period.fromString(string); + }, errorParam); + }); + } + + verifyFail('missing slash', '1997-01-01T18:30:20Z1997-01-02T07:00:00Z', /Invalid string value/); + verifyFail('invalid start date', 'some time before/1997-01-02T07:00:00Z', /invalid date-time value/); + verifyFail('invalid end param', '1997-01-02T07:00:00Z/some time after', /invalid date-time value/); + verifyFail('invalid end param that might be a duration', '1997-01-02T07:00:00Z/Psome time after', /invalid duration value/); + + verify('1997-01-01T18:30:20Z/1997-01-02T07:00:00Z', '19970101T183020Z/19970102T070000Z', { + start: { + year: 1997, + month: 1, + day: 1, + hour: 18, + minute: 30, + second: 20 + }, + + end: { + year: 1997, + month: 1, + day: 2, + hour: 7 + }, + + duration: null, + calculatedDuration: { + isNegative: false, + hours: 12, + minutes: 29, + seconds: 40 + }, + calculatedEnd: { + year: 1997, + month: 1, + day: 2, + hour: 7 + }, + }); + + verify('1997-01-01T18:00:00Z/PT5H30M', '19970101T180000Z/PT5H30M', { + start: { + year: 1997, + month: 1, + day: 1, + hour: 18 + }, + duration: { + isNegative: false, + hours: 5, + minutes: 30 + }, + end: null, + calculatedDuration: { + isNegative: false, + hours: 5, + minutes: 30 + }, + calculatedEnd: { + year: 1997, + month: 1, + day: 1, + hour: 23, + minute: 30 + } + }); + + }); + + suite('#fromData', function() { + test('valid start,end', function() { + let subject = ICAL.Period.fromData({ + start: start, + end: end + }); + + assert.hasProperties(subject.start, start, 'start date'); + assert.hasProperties(subject.end, end, 'end date'); + assert.isNull(subject.duration); + }); + test('valid start,duration', function() { + let subject = ICAL.Period.fromData({ + start: start, + duration: duration, + }); + + assert.hasProperties(subject.start, start, 'start date'); + assert.isNull(subject.end); + assert.hasProperties(subject.duration, duration, 'duration'); + }); + + test('end value exists but is null', function() { + let subject = ICAL.Period.fromData({ + start: start, + end: null + }); + assert.hasProperties(subject.start, start, 'start date'); + assert.isNull(subject.end); + assert.isNull(subject.duration); + }); + + test('start value exists but is null', function() { + let subject = ICAL.Period.fromData({ + start: null, + duration: duration, + }); + assert.isNull(subject.start); + assert.isNull(subject.end); + assert.hasProperties(subject.duration, duration, 'duration'); + }); + + test('duration value exists but is null', function() { + let subject = ICAL.Period.fromData({ + start: start, + duration: null, + }); + assert.hasProperties(subject.start, start, 'start date'); + assert.isNull(subject.end); + assert.isNull(subject.duration); + }); + + test('start,end and duration', function() { + assert.throws(function() { + ICAL.Period.fromData({ + start: start, + end: end, + duration: duration + }); + }, /cannot accept both end and duration/); + }); + + test('start,end and duration but one is null', function() { + let subject = ICAL.Period.fromData({ + start: start, + end: null, + duration: duration + }); + assert.hasProperties(subject.start, start, 'start date'); + assert.isNull(subject.end); + assert.hasProperties(subject.duration, duration, 'duration'); + }); + + test('invalid start value', function() { + assert.throws(function() { + ICAL.Period.fromData({ + start: '1970-01-02T03:04:05Z', + end: end + }); + }, /start must be an instance/); + }); + test('invalid end value', function() { + assert.throws(function() { + ICAL.Period.fromData({ + start: start, + end: '1970-01-02T03:04:05Z' + }); + }, /end must be an instance/); + }); + test('invalid duration value', function() { + assert.throws(function() { + ICAL.Period.fromData({ + start: start, + duration: 'PT1S' + }); + }, /duration must be an instance/); + }); + }); + + suite('#toString', function() { + test('start,end', function() { + let subject = ICAL.Period.fromData({ + start: start, + end: end + }); + assert.equal(subject.toString(), '1970-01-02T03:04:05Z/1970-01-02T03:04:05Z'); + }); + test('start,duration', function() { + let subject = ICAL.Period.fromData({ + start: start, + duration: duration + }); + assert.equal(subject.toString(), '1970-01-02T03:04:05Z/PT3H2M1S'); + }); + }); + + suite("generating jCal", function() { + test("jCal from parser", function() { + let prop = ICAL.parse.property("FREEBUSY:20140401T010101/PT1H"); + let val = prop[3]; + assert.deepEqual(val, ["2014-04-01T01:01:01", "PT1H"]); + }); + test("jCal from property", function() { + let prop = ICAL.Property.fromString("FREEBUSY:20140401T010101/PT1H"); + let val = prop.getFirstValue().toJSON(); + assert.deepEqual(val, ["2014-04-01T01:01:01", "PT1H"]); + }); + }); + + suite("#clone", function() { + test('cloned start/duration', function() { + let subjectstart = start.clone(); + let subjectduration = duration.clone(); + let subject1 = ICAL.Period.fromData({ start: subjectstart, duration: subjectduration }); + let subject2 = subject1.clone(); + subjectstart.hour++; + subjectduration.hours++; + + assert.equal(subject1.start.hour, 4); + assert.equal(subject2.start.hour, 3); + + assert.equal(subject1.duration.hours, 4); + assert.equal(subject2.duration.hours, 3); + }); + test('cloned start/end', function() { + let subjectstart = start.clone(); + let subjectend = end.clone(); + let subject1 = ICAL.Period.fromData({ start: subjectstart, end: subjectend }); + let subject2 = subject1.clone(); + subjectstart.hour++; + subjectend.hour++; + + assert.equal(subject1.start.hour, 4); + assert.equal(subject2.start.hour, 3); + + assert.equal(subject1.end.hour, 4); + assert.equal(subject2.end.hour, 3); + }); + test('cloned empty object', function() { + // most importantly, this shouldn't throw. + let subject1 = ICAL.Period.fromData(); + let subject2 = subject1.clone(); + + assert.equal(subject1.start, subject2.start); + assert.equal(subject1.end, subject2.end); + assert.equal(subject1.duration, subject2.duration); + }); + }); +}); diff --git a/test/property_test.js b/test/property_test.js new file mode 100644 index 0000000..713ebe7 --- /dev/null +++ b/test/property_test.js @@ -0,0 +1,555 @@ +suite('Property', function() { + let fixtures; + + setup(function() { + fixtures = { + component: ['vevent', [], []], + vcardComponent: ['vcard', [], []], + + noValue: [ + 'x-foo', + { prop: 'prop' }, + 'text' + ], + + textProp: [ + 'description', + {}, + 'text', + 'foo' + ], + + withParams: [ + 'x-foo', + { + 'rsvp': 'TRUE', + 'meta': 'foo' + }, + 'date', + '2012-10-01' + ], + + decoratedMultiValue: [ + 'rdate', + {}, + 'date', + '2012-10-10', + '2012-10-11' + ], + + mutliTextValue: [ + 'categories', + {}, + 'text', + 'one', + 'two', + 'three' + ] + }; + }); + + suite('initialization', function() { + + test('undecorated', function() { + let subject = new ICAL.Property( + fixtures.textProp, + new ICAL.Component(fixtures.component) + ); + + assert.equal(subject.jCal, fixtures.textProp); + assert.equal(subject.name, 'description'); + assert.equal(subject.type, 'text'); + + assert.isFalse(subject.isDecorated); + }); + + test('multi value', function() { + let subject = new ICAL.Property('categories'); + assert.isTrue( + subject.isMultiValue, 'is multiValue' + ); + + subject = new ICAL.Property('url'); + assert.isFalse( + subject.isMultiValue, 'is not multiValue' + ); + }); + + test('structured value', function() { + let subject = new ICAL.Property('request-status'); + assert.isTrue( + subject.isStructuredValue, 'is structured value' + ); + + subject = new ICAL.Property('url'); + assert.isFalse( + subject.isStructuredValue, 'is not structured value' + ); + }); + + test('decorated', function() { + let subject = new ICAL.Property( + fixtures.withParams, + new ICAL.Component(fixtures.component) + ); + + assert.isTrue(subject.isDecorated); + }); + + test('new property by name with type', function() { + let subject = new ICAL.Property('dtstart'); + assert.equal(subject.type, 'date-time'); + assert.equal(subject.jCal[2], 'date-time'); + assert.equal(subject._designSet, ICAL.design.icalendar); + }); + + test('new vcard property without parent (unknown type)', function() { + let subject = new ICAL.Property('anniversary'); + assert.equal(subject.type, 'unknown'); + assert.equal(subject.jCal[2], 'unknown'); + assert.equal(subject._designSet, ICAL.design.icalendar); + }); + + test('new vcard property with vcard parent (known type)', function() { + let parent = new ICAL.Component(fixtures.vcardComponent); + let subject = new ICAL.Property('anniversary', parent); + assert.equal(subject.type, 'date-and-or-time'); + assert.equal(subject.jCal[2], 'date-and-or-time'); + assert.equal(subject._designSet, ICAL.design.vcard); + }); + + test('custom design value without defaultType', function() { + ICAL.design.defaultSet.property.custom = {}; + let subject = new ICAL.Property('custom'); + assert.equal(subject.type, ICAL.design.defaultType); + assert.equal(subject.jCal[2], ICAL.design.defaultType); + delete ICAL.design.defaultSet.property.custom; + }); + + test('new property by name (typeless)', function() { + let subject = new ICAL.Property( + 'description' + ); + + assert.equal( + subject.name, + 'description' + ); + + assert.equal(subject.type, 'text'); + assert.equal(subject.jCal[2], 'text'); + + assert.ok(!subject.getFirstValue()); + }); + + test('types change when changing design set', function() { + let property = new ICAL.Property('fn'); + let component = new ICAL.Component('vcard'); + + assert.equal(property._designSet, ICAL.design.defaultSet); + assert.equal(property.type, 'unknown'); + + component.addProperty(property); + assert.equal(property._designSet, ICAL.design.vcard); + assert.equal(property.type, 'text'); + }); + + suite('#fromString', function() { + test('x-prop with known type', function() { + let prop = ICAL.Property.fromString("X-FOO;VALUE=BOOLEAN:TRUE"); + assert.equal(prop.name, "x-foo"); + assert.equal(prop.type, "boolean"); + assert.isTrue(prop.getFirstValue()); + }); + + test("invalid prop", function() { + assert.throws(function() { + ICAL.Property.fromString("BWAHAHAHAHA"); + }, /invalid line/); + }); + }); + }); + + test('#getParameter', function() { + let subject = new ICAL.Property( + fixtures.withParams + ); + + assert.equal(subject.getParameter('rsvp'), 'TRUE'); + assert.equal(subject.getParameter('wtf'), undefined); + }); + + suite('#getFirstParameter', function() { + test('with multivalue parameter', function() { + let subject = new ICAL.Property('categories'); + + subject.setParameter('categories', ['Home', 'Work']); + + assert.equal(subject.getFirstParameter('categories'), 'Home'); + }); + + test('with string parameter', function() { + let subject = new ICAL.Property( + fixtures.withParams + ); + + assert.equal(subject.getFirstParameter('rsvp'), 'TRUE'); + }); + }); + + test('#removeParameter', function() { + let subject = new ICAL.Property( + fixtures.withParams + ); + + subject.removeParameter('rsvp'); + assert.ok(!subject.getParameter('rsvp')); + }); + + test('#setParameter', function() { + let subject = new ICAL.Property( + fixtures.textProp + ); + + subject.setParameter( + 'my-prop', + 'woot?' + ); + + assert.equal( + subject.getParameter('my-prop'), + 'woot?' + ); + + assert.deepEqual( + subject.jCal[1], + { 'my-prop': 'woot?' } + ); + }); + + test('#setMultiValueParameterByString', function() { + let subject = new ICAL.Property( + fixtures.withParams + ); + + subject.setParameter( + 'member', + 'mailto:users@example.net' + ); + + assert.equal( + subject.getParameter('member')[0], + 'mailto:users@example.net' + ); + }); + + test('#setMultiValueParameter', function() { + let subject = new ICAL.Property( + fixtures.withParams + ); + + subject.setParameter( + 'member', + ['mailto:users@example.net'] + ); + + assert.equal( + subject.getParameter('member')[0], + 'mailto:users@example.net' + ); + }); + + suite('getFirstValue', function() { + + test('with no value', function() { + let subject = new ICAL.Property( + fixtures.noValue + ); + + assert.ok(!subject.getFirstValue()); + }); + + test('with decorated type', function() { + let subject = new ICAL.Property( + fixtures.withParams + ); + + let value = subject.getFirstValue(); + + assert.instanceOf(value, ICAL.Time); + //2012-10-01 + assert.hasProperties( + value, + { year: 2012, month: 10, day: 1, isDate: true }, + 'property correctness' + ); + + assert.equal( + subject.getFirstValue(), + subject.getFirstValue(), + 'decorated equality' + ); + }); + + test('without decorated type', function() { + let subject = new ICAL.Property(fixtures.textProp); + let value = subject.getFirstValue(); + + assert.equal( + value, + subject.jCal[3] + ); + }); + }); + + test('#resetType', function() { + let subject = new ICAL.Property('dtstart'); + subject.setValue(new ICAL.Time({ year: 2012, hour: 10, minute: 1 })); + + assert.equal(subject.type, 'date-time'); + + subject.resetType('date'); + assert.equal(subject.type, 'date'); + + assert.ok(!subject.getFirstValue()); + subject.setValue(new ICAL.Time({ year: 2012 })); + }); + + suite('#getDefaultType', function() { + test('known type', function() { + let subject = new ICAL.Property('dtstart'); + subject.setValue(new ICAL.Time({ year: 2012, hour: 20 })); + + assert.equal(subject.type, 'date-time'); + assert.equal(subject.getDefaultType(), 'date-time'); + + subject.setValue(new ICAL.Time({ year: 2012 })); + + assert.equal(subject.type, 'date'); + assert.equal(subject.getDefaultType(), 'date-time'); + }); + + test('unknown type', function() { + let subject = new ICAL.Property('x-unknown'); + subject.setValue(new ICAL.Time({ year: 2012, hour: 20 })); + + assert.equal(subject.getFirstValue().icaltype, 'date-time'); + assert.equal(subject.type, 'date-time'); + assert.equal(subject.getDefaultType(), 'unknown'); + }); + + test('vcard type', function() { + let parent = new ICAL.Component(fixtures.vcardComponent); + let subject = new ICAL.Property('anniversary', parent); + subject.resetType('text'); + + assert.equal(subject.getDefaultType(), 'date-and-or-time'); + }); + }); + + suite('#getFirstValue', function() { + test('with value', function() { + let subject = new ICAL.Property('description'); + subject.setValue('foo'); + + assert.equal(subject.getFirstValue(), 'foo'); + }); + + test('without value', function() { + let subject = new ICAL.Property('dtstart'); + assert.ok(!subject.getFirstValue()); + }); + }); + + suite('#getValues', function() { + test('decorated', function() { + let subject = new ICAL.Property( + fixtures.decoratedMultiValue + ); + + let result = subject.getValues(); + assert.lengthOf(result, 2); + + // 2012-10-10 + assert.hasProperties( + result[0], + { + year: 2012, + month: 10, + day: 10, + isDate: true + } + ); + + //2012-10-11 + assert.hasProperties( + result[1], + { + year: 2012, + month: 10, + day: 11, + isDate: true + } + ); + }); + + test('undecorated', function() { + let subject = new ICAL.Property( + fixtures.mutliTextValue + ); + + let result = subject.getValues(); + assert.lengthOf(result, 3); + assert.deepEqual( + result, + ['one', 'two', 'three'] + ); + }); + + test('single value', function() { + let subject = new ICAL.Property( + fixtures.textProp + ); + assert.deepEqual( + subject.getValues(), + [subject.jCal[3]] + ); + }); + + test('no values', function() { + let subject = new ICAL.Property(fixtures.noValue); + assert.deepEqual(subject.getValues(), []); + assert.equal(subject.toICALString(), "X-FOO;PROP=prop:"); + }); + + test('foldable value', function() { + let subject = new ICAL.Property(fixtures.textProp); + assert.deepEqual(subject.getValues(), ['foo']); + assert.equal(subject.toICALString(), "DESCRIPTION:foo"); + // Fold length should not fold the property here + let oldLength = ICAL.foldLength; + ICAL.foldLength = 1; + assert.equal(subject.toICALString(), "DESCRIPTION:foo"); + ICAL.foldLength = oldLength; + }); + }); + + suite('#setValues', function() { + test('decorated value', function() { + let subject = new ICAL.Property('rdate'); + let undecorate = ICAL.design.icalendar.value['date-time'].undecorate; + + let values = [ + new ICAL.Time({ year: 2012, month: 1 }), + new ICAL.Time({ year: 2012, month: 1 }) + ]; + + subject.setValues(values); + + assert.deepEqual( + subject.jCal.slice(3), + [undecorate(values[0]), undecorate(values[1])] + ); + + assert.equal( + subject.getFirstValue(), + values[0] + ); + }); + + test('text', function() { + let subject = new ICAL.Property('categories'); + + subject.setValues(['a', 'b', 'c']); + + assert.deepEqual( + subject.getValues(), + ['a', 'b', 'c'] + ); + + subject.setValues(['a']); + assert.deepEqual(subject.getValues(), ['a']); + }); + }); + + suite('#setValue', function() { + + test('decorated value as string', function() { + let subject = new ICAL.Property( + 'dtstart' + ); + + subject.setValue('2012-09-01T13:00:00'); + let value = subject.getFirstValue(); + + assert.equal(subject.type, 'date-time'); + assert.instanceOf(value, ICAL.Time); + + assert.hasProperties(value, { + year: 2012, + month: 9, + day: 1, + hour: 13 + }); + }); + + test('decorated value as object', function() { + let subject = new ICAL.Property( + 'dtstart' + ); + + let time = new ICAL.Time({ + year: 2012, + month: 1, + day: 5 + }); + + subject.setValue(time); + assert.equal(subject.type, 'date'); + + assert.equal( + subject.jCal[3], + ICAL.design.icalendar.value.date.undecorate(time) + ); + + assert.equal( + subject.getFirstValue(), + time + ); + }); + + test('text', function() { + let subject = new ICAL.Property('description'); + assert.ok(!subject.getFirstValue()); + subject.setValue('xxx'); + assert.equal(subject.getFirstValue(), 'xxx'); + }); + + test('multivalue property', function() { + let subject = new ICAL.Property("categories"); + subject.setValues(["work", "play"]); + subject.setValue("home"); + assert.deepEqual(subject.getValues(), ["home"]); + assert.equal(subject.getFirstValue(), "home"); + }); + + test('single-value property setting multiple values', function() { + let subject = new ICAL.Property("location"); + assert.throws(function() { + subject.setValues(["foo", "bar"]); + }, 'does not not support mulitValue'); + }); + }); + + test('#toJSON', function() { + let subject = new ICAL.Property(['description', {}, 'text', 'foo']); + + assert.deepEqual(subject.toJSON(), subject.jCal); + + let fromJSON = new ICAL.Property( + JSON.parse(JSON.stringify(subject)) + ); + + assert.deepEqual(fromJSON.jCal, subject.jCal); + }); +}); diff --git a/test/recur_expansion_test.js b/test/recur_expansion_test.js new file mode 100644 index 0000000..eb5d37d --- /dev/null +++ b/test/recur_expansion_test.js @@ -0,0 +1,332 @@ +suite('recur_expansion', function() { + let subject, primary; + + function createSubject(file) { + setup(async function() { + let icsData = await testSupport.loadSample(file); + let exceptions = []; + + await new Promise((resolve) => { + let parse = new ICAL.ComponentParser(); + + parse.onevent = function(event) { + if (event.isRecurrenceException()) { + exceptions.push(event); + } else { + primary = event; + } + }; + + parse.oncomplete = function() { + exceptions.forEach(primary.relateException, primary); + subject = new ICAL.RecurExpansion({ + component: primary.component, + dtstart: primary.startDate + }); + + resolve(); + }; + parse.process(icsData); + }); + + }); + } + + createSubject('recur_instances.ics'); + + suite('initialization', function() { + test('successful', function() { + assert.deepEqual( + subject.last.toJSDate(), + new Date('2012-10-02T17:00:00Z') + ); + + assert.instanceOf(subject.ruleIterators, Array); + assert.ok(subject.exDates); + }); + + test('invalid', function() { + assert.throws(() => new ICAL.RecurExpansion({}), ".dtstart (ICAL.Time) must be given"); + assert.throws(() => { + return new ICAL.RecurExpansion({ + dtstart: ICAL.Time.now() + }); + }, ".ruleIterators or .component must be given"); + }); + + test('default', function() { + let dtstart = ICAL.Time.fromData({ + year: 2012, + month: 2, + day: 2 + }); + let expansion = new ICAL.RecurExpansion({ + dtstart: dtstart, + ruleIterators: [] + }); + + assert.lengthOf(expansion.ruleDates, 0); + assert.lengthOf(expansion.exDates, 0); + assert.isFalse(expansion.complete); + + assert.deepEqual(expansion.toJSON(), { + ruleIterators: [], + ruleDates: [], + exDates: [], + ruleDateInc: undefined, + exDateInc: undefined, + dtstart: dtstart.toJSON(), + last: dtstart.toJSON(), + complete: false + }); + }); + }); + + suite('#_ensureRules', function() { + test('.ruleDates', function() { + let expected = [ + new Date('2012-11-05T18:00:00.000Z'), + new Date('2012-11-10T18:00:00.000Z'), + new Date('2012-11-30T18:00:00.000Z') + ]; + + + let dates = subject.ruleDates.map(function(time) { + return time.toJSDate(); + }); + + assert.deepEqual(dates, expected); + }); + + test('.exDates', function() { + let expected = [ + new Date('2012-12-04T18:00:00.000Z'), + new Date('2013-02-05T18:00:00.000Z'), + new Date('2013-04-02T17:00:00.000Z') + ]; + + let dates = subject.exDates.map(function(time) { + return time.toJSDate(); + }); + + assert.deepEqual(dates, expected); + }); + }); + + suite('#_nextRecurrenceIter', function() { + let component; + + setup(function() { + // setup a clean component with no rules + component = primary.component.toJSON(); + component = new ICAL.Component(component); + + // Simulate a more complicated event by using + // the original as a base and adding more complex rrule's + component.removeProperty('rrule'); + }); + + test('when rule ends', function() { + let start = { + year: 2012, + month: 1, + day: 1 + }; + + component.removeAllProperties('rdate'); + component.removeAllProperties('exdate'); + component.addPropertyWithValue('rrule', { freq: "WEEKLY", count: 3, byday: ["SU"] }); + + let expansion = new ICAL.RecurExpansion({ + component: component, + dtstart: start + }); + + let expected = [ + new Date(2012, 0, 1), + new Date(2012, 0, 8), + new Date(2012, 0, 15) + ]; + + let max = 10; + let i = 0; + let next; + let dates = []; + + while (i++ <= max && (next = expansion.next())) { + dates.push(next.toJSDate()); + } + + assert.deepEqual(dates, expected); + }); + + test('multiple rules', function() { + component.addPropertyWithValue('rrule', { freq: "MONTHLY", bymonthday: [13] }); + component.addPropertyWithValue('rrule', { freq: "WEEKLY", byday: ["TH"] }); + + let start = ICAL.Time.fromData({ + year: 2012, + month: 2, + day: 2 + }); + + let expansion = new ICAL.RecurExpansion({ + component: component, + dtstart: start + }); + + let expected = [ + new Date(2012, 1, 2), + new Date(2012, 1, 9), + new Date(2012, 1, 13), + new Date(2012, 1, 16), + new Date(2012, 1, 23) + ]; + + let inc = 0; + let max = expected.length; + let next; + let dates = []; + + while (inc++ < max) { + next = expansion._nextRecurrenceIter(); + dates.push(next.last.toJSDate()); + next.next(); + } + + assert.deepEqual(dates, expected); + }); + + }); + + suite('#next', function() { + // I use JS dates widely because it is much easier + // to compare them via chai's deepEquals function + let expected = [ + new Date('2012-10-02T17:00:00.000Z'), + new Date('2012-11-05T18:00:00.000Z'), + new Date('2012-11-06T18:00:00.000Z'), + new Date('2012-11-10T18:00:00.000Z'), + new Date('2012-11-30T18:00:00.000Z'), + new Date('2013-01-01T18:00:00.000Z') + ]; + + test('6 items', function() { + let dates = []; + let max = 6; + let inc = 0; + let next; + + while (inc++ < max && (next = subject.next())) { + dates.push(next.toJSDate()); + } + + assert.deepEqual( + dates, + expected + ); + }); + }); + + suite('#next - finite', function() { + createSubject('recur_instances_finite.ics'); + + test('until complete', function() { + let max = 100; + let inc = 0; + let next; + + let dates = []; + let expected = [ + new Date('2012-10-02T17:00:00.000Z'), + new Date('2012-11-05T18:00:00.000Z'), + new Date('2012-11-06T18:00:00.000Z'), + new Date('2012-11-10T18:00:00.000Z'), + new Date('2012-12-04T18:00:00.000Z') + ]; + + while (inc++ < max && (next = subject.next())) { + dates.push(next.toJSDate()); + } + + // round trip + subject = new ICAL.RecurExpansion(subject.toJSON()); + + while (inc++ < max && (next = subject.next())) { + dates.push(next.toJSDate()); + } + + assert.deepEqual(dates, expected); + assert.isTrue(subject.complete, 'complete'); + }); + }); + + + suite('#toJSON', function() { + // While other tests in this file don't require specifying a timezone, we + // need to do so here because we're building the `RecurExpansion` from a + // limited subset of the ICS which does not include the timezone definition. + testSupport.useTimezones('America/Los_Angeles'); + + test('from start', function() { + let json = subject.toJSON(); + let newIter = new ICAL.RecurExpansion(json); + let cur = 0; + + while (cur++ < 10) { + assert.deepEqual( + subject.next().toJSDate(), + newIter.next().toJSDate(), + 'failed compare at #' + cur + ); + } + }); + + test('from two iterations', function() { + subject.next(); + subject.next(); + + let json = subject.toJSON(); + let newIter = new ICAL.RecurExpansion(json); + let cur = 0; + + while (cur++ < 10) { + assert.deepEqual( + subject.next().toJSDate(), + newIter.next().toJSDate(), + 'failed compare at #' + cur + ); + } + }); + + }); + + suite('event without recurrences', function() { + createSubject('minimal.ics'); + + test('iterate', function() { + let dates = []; + let next; + + let expected = primary.startDate.toJSDate(); + + while ((next = subject.next())) { + dates.push(next.toJSDate()); + } + + assert.deepEqual(dates[0], expected); + assert.lengthOf(dates, 1); + assert.isTrue(subject.complete); + + // json check + subject = new ICAL.RecurExpansion( + subject.toJSON() + ); + + assert.isTrue(subject.complete, 'complete after json'); + assert.ok(!subject.next(), 'next value'); + }); + + }); + +}); diff --git a/test/recur_iterator_test.js b/test/recur_iterator_test.js new file mode 100644 index 0000000..4d36a88 --- /dev/null +++ b/test/recur_iterator_test.js @@ -0,0 +1,1266 @@ +suite('recur_iterator', function() { + suite('#fromData', function() { + test("required rule", function() { + assert.throws(function() { + new ICAL.RecurIterator({}); // eslint-disable-line no-new + }, /iterator requires a \(ICAL.Recur\) rule/); + }); + test("required time", function() { + assert.throws(function() { + new ICAL.RecurIterator({ // eslint-disable-line no-new + rule: new ICAL.Recur() + }); + }, /iterator requires a \(ICAL.Time\) dtstart/); + }); + }); + + suite('#toJSON', function() { + let recur, iterator; + + setup(function() { + let start = ICAL.Time.fromString('2012-02-01T09:00:00'); + recur = ICAL.Recur.fromString('FREQ=MONTHLY;COUNT=12;INTERVAL=3'); + iterator = recur.iterator(start); + }); + + test('completed', function() { + while (iterator.next()) { + // Continue until completed + } + + assert.isTrue(iterator.completed, 'is completed'); + + let json = iterator.toJSON(); + let newIter = new ICAL.RecurIterator(json); + + assert.equal(newIter.next(), null, 'new iter next'); + assert.isTrue(newIter.completed, true, 'new iter completed'); + }); + + test('INTERVAL: mid iteration (two iterations)', function() { + iterator.next(); + iterator.next(); + + let json = iterator.toJSON(); + let newIter = new ICAL.RecurIterator(json); + let inc = 0; + + while (inc++ < 8) { + assert.deepEqual( + iterator.next().toJSDate(), + newIter.next().toJSDate(), + 'failed #' + inc + ); + } + }); + + test('from the begining of iteration', function() { + let expected = { + rule: iterator.rule.toJSON(), + dtstart: iterator.dtstart.toJSON(), + by_data: iterator.by_data, + days: iterator.days, + initialized: true, + last: iterator.last.toJSON(), + by_indices: iterator.by_indices, + occurrence_number: iterator.occurrence_number + }; + + let json = iterator.toJSON(); + assert.deepEqual(json, expected); + + let newIter = new ICAL.RecurIterator(json); + let inc = 0; + + while (inc++ < 10) { + assert.deepEqual( + newIter.next().toJSDate(), + iterator.next().toJSDate(), + 'iterator equality #' + inc + ); + } + }); + + }); + + suite('#normalizeByMonthDayRules', function() { + let recur, iterator; + + setup(function() { + let start = ICAL.Time.fromString('2012-02-01T09:00:00'); + recur = ICAL.Recur.fromString('FREQ=MONTHLY;COUNT=2'); + iterator = recur.iterator(start); + }); + + test('positive rules', function() { + let result = iterator.normalizeByMonthDayRules( + 2012, 2, [21, 15] + ); + + assert.deepEqual(result, [15, 21]); + }); + + test('when given zero', function() { + let result = iterator.normalizeByMonthDayRules( + 2012, 2, [21, 0] + ); + + assert.deepEqual(result, [21]); + }); + + test('extra days', function() { + let result = iterator.normalizeByMonthDayRules( + 2012, 2, [1, 31] + ); + + assert.deepEqual(result, [1]); + }); + + test('negative and positive days', function() { + let result = iterator.normalizeByMonthDayRules( + 2012, 2, [1, -1] + ); + + assert.deepEqual(result, [1, 29]); + }); + + test('duplicates', function() { + let result = iterator.normalizeByMonthDayRules( + // -29 === 1st day + 2012, 2, [2, 2, 1, -29] + ); + + assert.deepEqual(result, [1, 2]); + }); + }); + + function testRRULE(ruleString, options) { + let runner = options.only ? test.only : test; + runner(ruleString, function() { + if (!options.dtStart) { + options.dtStart = options.dates[0]; + } + + let start = ICAL.Time.fromString(options.dtStart); + let recur = ICAL.Recur.fromString(ruleString); + + if (options.throws) { + assert.throws(function() { + recur.iterator(start); + }); + return; + } + + let iterator = recur.iterator(start); + + if (options.noInstance) { + assert.equal(iterator.next(), null); + assert.ok(iterator.completed); + return; + } + + let inc = 0; + let dates = []; + let next, max; + + if ('max' in options) { + max = options.max; + } else if (recur.isFinite()) { + max = options.dates.length + 1; + } else { + max = options.dates.length; + } + + assert.equal(recur.isFinite(), options.byCount || options.until || false); + assert.equal(recur.isByCount(), options.byCount || false); + + while (inc++ < max && (next = iterator.next())) { + dates.push(next.toString()); + } + assert.deepEqual(dates, options.dates || []); + }); + } + testRRULE.only = function(ruleString, options) { + options.only = true; + testRRULE(ruleString, options); + }; + + suite("#recurrence rules", function() { + suite('SECONDLY/MINUTELY/HOURLY', function() { + // Simple secondly + testRRULE('FREQ=SECONDLY;INTERVAL=3;COUNT=3', { + byCount: true, + dates: [ + '2015-04-30T08:00:00', + '2015-04-30T08:00:03', + '2015-04-30T08:00:06' + ] + }); + + // Simple minutely + testRRULE('FREQ=MINUTELY;INTERVAL=3;COUNT=3', { + byCount: true, + dates: [ + '2015-04-30T08:00:00', + '2015-04-30T08:03:00', + '2015-04-30T08:06:00' + ] + }); + + //simple hourly + testRRULE('FREQ=HOURLY;INTERVAL=3;COUNT=3', { + byCount: true, + dates: [ + '2015-04-30T08:00:00', + '2015-04-30T11:00:00', + '2015-04-30T14:00:00' + ] + }); + }); + + suite('DAILY', function() { + //daily for 10 occurrences' + testRRULE('FREQ=DAILY;COUNT=10', { + byCount: true, + dates: [ + '2012-09-01T09:00:00', + '2012-09-02T09:00:00', + '2012-09-03T09:00:00', + '2012-09-04T09:00:00', + '2012-09-05T09:00:00', + '2012-09-06T09:00:00', + '2012-09-07T09:00:00', + '2012-09-08T09:00:00', + '2012-09-09T09:00:00', + '2012-09-10T09:00:00' + ] + }); + + //every other day - forever + testRRULE('FREQ=DAILY;INTERVAL=2', { + dates: [ + '2012-09-01T09:00:00', + '2012-09-03T09:00:00', + '2012-09-05T09:00:00', + '2012-09-07T09:00:00', + '2012-09-09T09:00:00', + '2012-09-11T09:00:00', + '2012-09-13T09:00:00', + '2012-09-15T09:00:00', + '2012-09-17T09:00:00', + '2012-09-19T09:00:00' + ] + }); + + // every 10 days, 5 occurrences + testRRULE('FREQ=DAILY;INTERVAL=10;COUNT=5', { + byCount: true, + dates: [ + '2012-09-01T09:00:00', + '2012-09-11T09:00:00', + '2012-09-21T09:00:00', + '2012-10-01T09:00:00', + '2012-10-11T09:00:00' + ] + }); + + //daily on weekdays', + testRRULE('FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR', { + dates: [ + '2012-01-02T09:00:00', + '2012-01-03T09:00:00', + '2012-01-04T09:00:00', + '2012-01-05T09:00:00', + '2012-01-06T09:00:00', + '2012-01-09T09:00:00', + '2012-01-10T09:00:00', + '2012-01-11T09:00:00', + '2012-01-12T09:00:00', + '2012-01-13T09:00:00' + ] + }); + }); + + suite('WEEKLY', function() { + // weekly until + testRRULE('FREQ=WEEKLY;UNTIL=20120424T065959Z;BYDAY=TU', { + until: true, + dates: [ + '2012-04-10T09:00:00', + '2012-04-17T09:00:00' + ] + }); + //weekly for 10 occurrences + testRRULE('FREQ=WEEKLY;COUNT=10', { + byCount: true, + dates: [ + '2012-01-05T09:00:00', + '2012-01-12T09:00:00', + '2012-01-19T09:00:00', + '2012-01-26T09:00:00', + '2012-02-02T09:00:00', + '2012-02-09T09:00:00', + '2012-02-16T09:00:00', + '2012-02-23T09:00:00', + '2012-03-01T09:00:00', + '2012-03-08T09:00:00' + ] + }); + + //Weekly until December 24, 2012' + testRRULE('FREQ=WEEKLY;UNTIL=20121224T000000Z', { + until: true, + dates: [ + '2012-11-15T00:00:00', + '2012-11-22T00:00:00', + '2012-11-29T00:00:00', + '2012-12-06T00:00:00', + '2012-12-13T00:00:00', + '2012-12-20T00:00:00' + ] + }); + + //every other week forever' + testRRULE('FREQ=WEEKLY;INTERVAL=2;WKST=SU', { + dates: [ + '2012-01-15T09:00:00', + '2012-01-29T09:00:00', + '2012-02-12T09:00:00' + ] + }); + + //weekly on tuesday and thursday for five weeks + testRRULE('FREQ=WEEKLY;COUNT=4;WKST=SU;BYDAY=TU,TH', { + dtStart: '2012-01-01T09:00:00', + byCount: true, + dates: [ + '2012-01-03T09:00:00', + '2012-01-05T09:00:00', + '2012-01-10T09:00:00', + '2012-01-12T09:00:00' + ] + }); + + //every other week on mo,we,fi until dec 24th 1997 + testRRULE('FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T090000Z;WKST=SU;BYDAY=MO,WE,FR', { + until: true, + dates: [ + '1997-09-01T09:00:00', '1997-09-03T09:00:00', '1997-09-05T09:00:00', + '1997-09-15T09:00:00', '1997-09-17T09:00:00', '1997-09-19T09:00:00', + '1997-09-29T09:00:00', '1997-10-01T09:00:00', '1997-10-03T09:00:00', + '1997-10-13T09:00:00', '1997-10-15T09:00:00', '1997-10-17T09:00:00', + '1997-10-27T09:00:00', '1997-10-29T09:00:00', '1997-10-31T09:00:00', + '1997-11-10T09:00:00', '1997-11-12T09:00:00', '1997-11-14T09:00:00', + '1997-11-24T09:00:00', '1997-11-26T09:00:00', '1997-11-28T09:00:00', + '1997-12-08T09:00:00', '1997-12-10T09:00:00', '1997-12-12T09:00:00', + '1997-12-22T09:00:00', '1997-12-24T09:00:00' + ] + }); + + /* TODO byweekno is not well supported + testRRULE('FREQ=WEEKLY;BYWEEKNO=2,4,6', { + dates: [ + '2015-01-11T08:00:00', // TODO the first occurrence is given twice + '2015-01-12T08:00:00', '2015-01-26T08:00:00', '2015-02-09T08:00:00', + '2016-01-11T08:00:00', '2016-01-25T08:00:00', '2016-02-08T08:00:00' + ] + }); + */ + + //weekly WKST changes output' + //MO + testRRULE('FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=MO', { + byCount: true, + dates: [ + '1997-08-05T09:00:00', + '1997-08-10T09:00:00', + '1997-08-19T09:00:00', + '1997-08-24T09:00:00' + ] + }); + + //'weekly WKST changes output' + //SU + testRRULE('FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=SU', { + byCount: true, + dates: [ + '1997-08-05T09:00:00', + '1997-08-17T09:00:00', + '1997-08-19T09:00:00', + '1997-08-31T09:00:00' + ] + }); + + // weekly on tuesday + testRRULE('FREQ=WEEKLY;BYDAY=TU', { + dates: [ + '2012-09-11T09:00:00', + '2012-09-18T09:00:00', + '2012-09-25T09:00:00', + '2012-10-02T09:00:00', + '2012-10-09T09:00:00' + ] + }); + + //buisness days for 31 occurances' + testRRULE('FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR', { + dates: [ + '2012-01-02T09:00:00', '2012-01-03T09:00:00', '2012-01-04T09:00:00', '2012-01-05T09:00:00', '2012-01-06T09:00:00', + '2012-01-09T09:00:00', '2012-01-10T09:00:00', '2012-01-11T09:00:00', '2012-01-12T09:00:00', '2012-01-13T09:00:00', + '2012-01-16T09:00:00', '2012-01-17T09:00:00', '2012-01-18T09:00:00', '2012-01-19T09:00:00', '2012-01-20T09:00:00', + '2012-01-23T09:00:00', '2012-01-24T09:00:00', '2012-01-25T09:00:00', '2012-01-26T09:00:00', '2012-01-27T09:00:00', + '2012-01-30T09:00:00', '2012-01-31T09:00:00', '2012-02-01T09:00:00', '2012-02-02T09:00:00', '2012-02-03T09:00:00', + '2012-02-06T09:00:00', '2012-02-07T09:00:00', '2012-02-08T09:00:00', '2012-02-09T09:00:00', '2012-02-10T09:00:00', + '2012-02-13T09:00:00' + ] + }); + }); + + suite('MONTHLY', function() { + //monthly on first friday for 10 occurrences + testRRULE('FREQ=MONTHLY;COUNT=10;BYDAY=1FR', { + dtStart: '2012-01-07T00:00:00', + byCount: true, + dates: [ + '2012-02-03T00:00:00', + '2012-03-02T00:00:00', + '2012-04-06T00:00:00', + '2012-05-04T00:00:00', + '2012-06-01T00:00:00', + '2012-07-06T00:00:00', + '2012-08-03T00:00:00', + '2012-09-07T00:00:00', + '2012-10-05T00:00:00', + '2012-11-02T00:00:00' + ] + }); + + //every thursday 31th forever' + testRRULE('FREQ=MONTHLY;BYDAY=TH;BYMONTHDAY=31', { + dtStart: '2012-01-31T09:00:00', + dates: [ + '2012-05-31T09:00:00', + '2013-01-31T09:00:00', + '2013-10-31T09:00:00' + ] + }); + + //every other month; first and last sunday for 4 occurrences + testRRULE('FREQ=MONTHLY;INTERVAL=2;COUNT=4;BYDAY=1SU,-1SU', { + dtStart: '2012-11-01T09:00:00', + byCount: true, + dates: [ + '2012-11-04T09:00:00', + '2012-11-25T09:00:00', + '2013-01-06T09:00:00', + '2013-01-27T09:00:00' + ] + }); + + //monthly third to last day of month forever + testRRULE('FREQ=MONTHLY;BYMONTHDAY=-3', { + dtStart: '2012-01-01T09:00:00', + dates: [ + '2012-01-29T09:00:00', + '2012-02-27T09:00:00', + '2012-03-29T09:00:00' + ] + }); + + + // monthly, the third instance of tu,we,th + testRRULE('FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=3', { + byCount: true, + dates: [ + '1997-09-04T09:00:00', + '1997-10-07T09:00:00', + '1997-11-06T09:00:00' + ] + }); + + //monthly, each month last day that is monday + testRRULE('FREQ=MONTHLY;BYMONTHDAY=-1;BYDAY=MO', { + dtStart: '2012-01-01T09:00:00', + dates: [ + '2012-04-30T09:00:00', + '2012-12-31T09:00:00' + ] + }); + + //every friday 13th forever' + testRRULE('FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13', { + dtStart: '2012-04-01T09:00:00', + dates: [ + '2012-04-13T09:00:00', + '2012-07-13T09:00:00', + '2013-09-13T09:00:00' + ] + }); + + //'Every 11th & 31st every month' + testRRULE('FREQ=MONTHLY;BYMONTHDAY=11,31', { + dtStart: '2013-04-01T08:00:00', + dates: [ + '2013-04-11T08:00:00', + '2013-05-11T08:00:00', + '2013-05-31T08:00:00', + '2013-06-11T08:00:00', + '2013-07-11T08:00:00', + '2013-07-31T08:00:00' + ] + }); + + //Every WE & SA the 6th, 20th & 31st every month + testRRULE('FREQ=MONTHLY;BYDAY=WE,SA;BYMONTHDAY=6,20,31', { + dtStart: '2013-07-01T08:00:00', + dates: [ + '2013-07-06T08:00:00', + '2013-07-20T08:00:00', + '2013-07-31T08:00:00', + '2013-08-31T08:00:00', + '2013-11-06T08:00:00', + '2013-11-20T08:00:00' + ] + }); + + //monthly, on the 3rd, BYMONTHDAY not set + testRRULE('FREQ=MONTHLY', { + dates: [ + '2013-04-03T08:00:00', + '2013-05-03T08:00:00', + '2013-06-03T08:00:00', + '2013-07-03T08:00:00', + '2013-08-03T08:00:00', + '2013-09-03T08:00:00' + ] + }); + + // monthly, on the 31st, BYMONTHDAY not set + testRRULE('FREQ=MONTHLY', { + dates: [ + '2013-01-31T08:00:00', + '2013-03-31T08:00:00', + '2013-05-31T08:00:00', + '2013-07-31T08:00:00', + '2013-08-31T08:00:00', + '2013-10-31T08:00:00' + ] + }); + + //Repeat Monthly every Wednesday, Friday and the third Monday + testRRULE('FREQ=MONTHLY;BYDAY=3MO,WE,FR', { + dates: [ + '2015-01-02T08:00:00', + '2015-01-07T08:00:00', + '2015-01-09T08:00:00', + '2015-01-14T08:00:00', + '2015-01-16T08:00:00', + '2015-01-19T08:00:00', + '2015-01-21T08:00:00', + '2015-01-23T08:00:00' + ] + }); + + //Repeat Monthly, the fifth Saturday (BYDAY=5SA) + testRRULE('FREQ=MONTHLY;BYDAY=5SA', { + dtStart: '2015-02-04T08:00:00', + dates: [ + '2015-05-30T08:00:00', + '2015-08-29T08:00:00', + '2015-10-31T08:00:00', + '2016-01-30T08:00:00', + '2016-04-30T08:00:00', + '2016-07-30T08:00:00' + ] + }); + + // Repeat Monthly, the fifth Wednesday every two months (BYDAY=5WE) + testRRULE('FREQ=MONTHLY;INTERVAL=2;BYDAY=5WE', { + dtStart: '2015-01-01T08:00:00', + dates: [ + '2015-07-29T08:00:00', + '2015-09-30T08:00:00', + '2016-03-30T08:00:00', + '2016-11-30T08:00:00', + '2017-03-29T08:00:00', + '2017-05-31T08:00:00' + ] + }); + + //Repeat Monthly, the 2nd Monday, 5th Wednesday and the 5th to last Saturday every month + testRRULE('FREQ=MONTHLY;BYDAY=2MO,-5WE,5SA', { + dates: [ + '2015-04-01T08:00:00', + '2015-04-13T08:00:00', + '2015-05-11T08:00:00', + '2015-05-30T08:00:00', + '2015-06-08T08:00:00', + '2015-07-01T08:00:00', + '2015-07-13T08:00:00' + ] + }); + + // from rfc -> the last work day of the month + testRRULE('FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1', { + dates: [ + '2015-06-30T08:00:00', + '2015-07-31T08:00:00', + '2015-08-31T08:00:00', + '2015-09-30T08:00:00', + '2015-10-30T08:00:00', + '2015-11-30T08:00:00' + ] + }); + + //BYMONTHDAY + testRRULE('FREQ=MONTHLY;BYMONTHDAY=1', { + dates: [ + '2015-01-01T08:00:00', + '2015-02-01T08:00:00', + '2015-03-01T08:00:00' + ] + }); + + // Last day and 31st day of the month. The last day could be the 31st, + // and this shouldn't throw an error. + testRRULE('FREQ=MONTHLY;BYMONTHDAY=-1,31', { + dtStart: '2022-01-01T08:00:00', + dates: [ + '2022-01-31T08:00:00', + '2022-02-28T08:00:00', + '2022-03-31T08:00:00', + '2022-04-30T08:00:00', + '2022-05-31T08:00:00', + '2022-06-30T08:00:00' + ] + }); + + // 31st day of the month, specified more than once. The repeated values + // should be collapsed to one. + testRRULE('FREQ=MONTHLY;BYMONTHDAY=31,31,31,31', { + dtStart: '2022-01-01T08:00:00', + dates: [ + '2022-01-31T08:00:00', + '2022-03-31T08:00:00', + '2022-05-31T08:00:00', + '2022-07-31T08:00:00', + '2022-08-31T08:00:00', + '2022-10-31T08:00:00', + '2022-12-31T08:00:00', + ] + }); + + // Weirdly ordered BYMONTHDAY. Occurrences should be in chronological order. + testRRULE('FREQ=MONTHLY;BYMONTHDAY=28,-4,11', { + dtStart: '2022-01-01T08:00:00', + dates: [ + '2022-01-11T08:00:00', + '2022-01-28T08:00:00', + '2022-02-11T08:00:00', + '2022-02-25T08:00:00', + '2022-02-28T08:00:00', + '2022-03-11T08:00:00', + '2022-03-28T08:00:00', + '2022-04-11T08:00:00', + '2022-04-27T08:00:00', + '2022-04-28T08:00:00', + ] + }); + + testRRULE('FREQ=MONTHLY;BYMONTHDAY=-4,28,11', { + dtStart: '2022-01-01T08:00:00', + dates: [ + '2022-01-11T08:00:00', + '2022-01-28T08:00:00', + '2022-02-11T08:00:00', + '2022-02-25T08:00:00', + '2022-02-28T08:00:00', + '2022-03-11T08:00:00', + '2022-03-28T08:00:00', + '2022-04-11T08:00:00', + '2022-04-27T08:00:00', + '2022-04-28T08:00:00', + ] + }); + + // Multiple BYMONTHDAYs, checks that we start on the right one. + testRRULE('FREQ=MONTHLY;BYMONTHDAY=6,12,18,24', { + dtStart: '2024-04-03T01:00:00', + dates: [ + '2024-04-06T01:00:00', + '2024-04-12T01:00:00', + '2024-04-18T01:00:00', + '2024-04-24T01:00:00', + '2024-05-06T01:00:00', + ] + }); + + testRRULE('FREQ=MONTHLY;BYMONTHDAY=6,12,18,24', { + dtStart: '2024-04-09T01:00:00', + dates: [ + '2024-04-12T01:00:00', + '2024-04-18T01:00:00', + '2024-04-24T01:00:00', + '2024-05-06T01:00:00', + '2024-05-12T01:00:00', + ] + }); + + testRRULE('FREQ=MONTHLY;BYMONTHDAY=6,12,18,24', { + dtStart: '2024-04-15T01:00:00', + dates: [ + '2024-04-18T01:00:00', + '2024-04-24T01:00:00', + '2024-05-06T01:00:00', + '2024-05-12T01:00:00', + '2024-05-18T01:00:00', + ] + }); + + testRRULE('FREQ=MONTHLY;BYMONTHDAY=6,12,18,24', { + dtStart: '2024-04-21T01:00:00', + dates: [ + '2024-04-24T01:00:00', + '2024-05-06T01:00:00', + '2024-05-12T01:00:00', + '2024-05-18T01:00:00', + '2024-05-24T01:00:00', + ] + }); + + testRRULE('FREQ=MONTHLY;BYMONTHDAY=6,12,18,24', { + dtStart: '2024-04-27T01:00:00', + dates: [ + '2024-05-06T01:00:00', + '2024-05-12T01:00:00', + '2024-05-18T01:00:00', + '2024-05-24T01:00:00', + '2024-06-06T01:00:00', + ] + }); + + // Last day of the month, monthly. + testRRULE('FREQ=MONTHLY;BYMONTHDAY=-1', { + dtStart: '2015-01-01T08:00:00', + dates: [ + '2015-01-31T08:00:00', + '2015-02-28T08:00:00', + '2015-03-31T08:00:00' + ] + }); + + // Last day of the month, every 3 months. + testRRULE('FREQ=MONTHLY;INTERVAL=3;BYMONTHDAY=-1', { + dtStart: '2022-06-01T08:00:00', + dates: [ + '2022-06-30T08:00:00', + '2022-09-30T08:00:00', + '2022-12-31T08:00:00', + '2023-03-31T08:00:00', + '2023-06-30T08:00:00', + '2023-09-30T08:00:00', + ] + }); + + // Second-to-last day of the month, every 3 months. + testRRULE('FREQ=MONTHLY;INTERVAL=3;BYMONTHDAY=-2', { + dtStart: '2022-06-01T08:00:00', + dates: [ + '2022-06-29T08:00:00', + '2022-09-29T08:00:00', + '2022-12-30T08:00:00', + '2023-03-30T08:00:00', + '2023-06-29T08:00:00', + '2023-09-29T08:00:00', + ] + }); + + // 31st day of the month, every two months. Starting on a month that has 31 days. + testRRULE('FREQ=MONTHLY;INTERVAL=2;BYMONTHDAY=31', { + dtStart: '2022-07-01T08:00:00', + dates: [ + '2022-07-31T08:00:00', + '2023-01-31T08:00:00', + '2023-03-31T08:00:00', + '2023-05-31T08:00:00', + '2023-07-31T08:00:00', + ] + }); + + // 31st day of the month, every two months. Starting on a month that has 30 days. + testRRULE('FREQ=MONTHLY;INTERVAL=2;BYMONTHDAY=31', { + dtStart: '2022-06-01T08:00:00', + dates: [ + '2022-08-31T08:00:00', + '2022-10-31T08:00:00', + '2022-12-31T08:00:00', + '2023-08-31T08:00:00', + '2023-10-31T08:00:00', + ] + }); + + // 31st day of the month, every two months. Starting with three invalid months. + testRRULE('FREQ=MONTHLY;INTERVAL=2;BYMONTHDAY=31', { + dtStart: '2022-02-01T08:00:00', + dates: [ + '2022-08-31T08:00:00', + '2022-10-31T08:00:00', + '2022-12-31T08:00:00', + '2023-08-31T08:00:00', + '2023-10-31T08:00:00', + ] + }); + + // Invalid rule. There's never a 31st of Feburary, check that this fails. + testRRULE('FREQ=MONTHLY;INTERVAL=12;BYMONTHDAY=31', { + dtStart: '2022-02-01T08:00:00', + throws: true, + }); + + // monthly + by month + testRRULE('FREQ=MONTHLY;BYMONTH=1,3,6,9,12', { + dates: [ + '2015-01-01T08:00:00', + '2015-03-01T08:00:00', + '2015-06-01T08:00:00', + '2015-09-01T08:00:00', + '2015-12-01T08:00:00' + ] + }); + + testRRULE('FREQ=MONTHLY;BYDAY=MO,FR;BYMONTHDAY=1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31;COUNT=4', { + dtStart: '2015-03-01T08:00:00Z', + byCount: true, + dates: [ + '2015-03-09T08:00:00Z', + '2015-03-13T08:00:00Z', + '2015-03-23T08:00:00Z', + '2015-03-27T08:00:00Z', + ] + }); + testRRULE('FREQ=MONTHLY;BYDAY=MO,FR;BYMONTHDAY=1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31;COUNT=4', { + dtStart: '2015-04-01T08:00:00Z', + byCount: true, + dates: [ + '2015-04-03T08:00:00Z', + '2015-04-13T08:00:00Z', + '2015-04-17T08:00:00Z', + '2015-04-27T08:00:00Z' + ] + }); + testRRULE('FREQ=MONTHLY;BYDAY=MO,SA;BYMONTHDAY=1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31;COUNT=4', { + dtStart: '2015-04-01T08:00:00Z', + byCount: true, + dates: [ + '2015-04-11T08:00:00Z', + '2015-04-13T08:00:00Z', + '2015-04-25T08:00:00Z', + '2015-04-27T08:00:00Z' + ] + }); + testRRULE('FREQ=MONTHLY;BYDAY=SU,FR;BYMONTHDAY=1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31;COUNT=9', { + dtStart: '2015-02-28T08:00:00Z', + byCount: true, + dates: [ + "2015-03-01T08:00:00Z", + "2015-03-13T08:00:00Z", + "2015-03-15T08:00:00Z", + "2015-03-27T08:00:00Z", + "2015-03-29T08:00:00Z", + "2015-04-03T08:00:00Z", + "2015-04-05T08:00:00Z", + "2015-04-17T08:00:00Z", + "2015-04-19T08:00:00Z" + ] + }); + }); + + suite('YEARLY', function() { + //yearly & by month with one by day + testRRULE('FREQ=YEARLY;BYMONTH=3;BYDAY=TU', { + dtStart: '1970-03-08T02:00:00', + dates: [ + '1970-03-10T02:00:00' + ] + }); + + //every monday in January, for 3 years + testRRULE('FREQ=YEARLY;UNTIL=20150131T090000Z;BYMONTH=1;BYDAY=MO', { + dtStart: '2012-05-01T09:00:00', + until: true, + dates: [ + '2013-01-07T09:00:00', + '2013-01-14T09:00:00', + '2013-01-21T09:00:00', + '2013-01-28T09:00:00', + '2014-01-06T09:00:00', + '2014-01-13T09:00:00', + '2014-01-20T09:00:00', + '2014-01-27T09:00:00', + '2015-01-05T09:00:00', + '2015-01-12T09:00:00', + '2015-01-19T09:00:00', + '2015-01-26T09:00:00' + ] + }); + + //Every year the last day of February (rule with BYMONTH) + testRRULE('FREQ=YEARLY;BYMONTHDAY=-1;BYMONTH=2', { + dates: [ + '2014-02-28T08:00:00', + '2015-02-28T08:00:00', + '2016-02-29T08:00:00', + '2017-02-28T08:00:00', + '2018-02-28T08:00:00', + '2019-02-28T08:00:00' + ] + }); + + //Every year the last day of April (rule without BYMONTH) + testRRULE('FREQ=YEARLY;BYMONTHDAY=-1', { + dates: [ + '2014-04-30T08:00:00', + '2015-04-30T08:00:00', + '2016-04-30T08:00:00', + '2017-04-30T08:00:00', + '2018-04-30T08:00:00', + '2019-04-30T08:00:00' + ] + }); + + //Yearly, every WE and FR of January and March (more BYMONTH and more BYDAY) + testRRULE('FREQ=YEARLY;BYMONTH=1,3;BYDAY=WE,FR', { + dates: [ + '2014-01-01T08:00:00', '2014-01-03T08:00:00', + '2014-01-08T08:00:00', '2014-01-10T08:00:00', + '2014-01-15T08:00:00', '2014-01-17T08:00:00', + '2014-01-22T08:00:00', '2014-01-24T08:00:00', + '2014-01-29T08:00:00', '2014-01-31T08:00:00', + '2014-03-05T08:00:00', '2014-03-07T08:00:00', + '2014-03-12T08:00:00', '2014-03-14T08:00:00', + '2014-03-19T08:00:00', '2014-03-21T08:00:00', + '2014-03-26T08:00:00', '2014-03-28T08:00:00' + ] + }); + + // Yearly, every day of January (one BYMONTH and more BYDAY + testRRULE('FREQ=YEARLY;BYMONTH=1;BYDAY=SU,MO,TU,WE,TH,FR,SA', { + dates: [ + '2014-01-01T08:00:00', + '2014-01-02T08:00:00', + '2014-01-03T08:00:00', + '2014-01-04T08:00:00', + '2014-01-05T08:00:00', + '2014-01-06T08:00:00', + '2014-01-07T08:00:00', + '2014-01-08T08:00:00', + '2014-01-09T08:00:00', + '2014-01-10T08:00:00', + '2014-01-11T08:00:00', + '2014-01-12T08:00:00', + '2014-01-13T08:00:00', + '2014-01-14T08:00:00', + '2014-01-15T08:00:00', + '2014-01-16T08:00:00', + '2014-01-17T08:00:00', + '2014-01-18T08:00:00', + '2014-01-19T08:00:00', + '2014-01-20T08:00:00', + '2014-01-21T08:00:00', + '2014-01-22T08:00:00', + '2014-01-23T08:00:00', + '2014-01-24T08:00:00', + '2014-01-25T08:00:00', + '2014-01-26T08:00:00', + '2014-01-27T08:00:00', + '2014-01-28T08:00:00', + '2014-01-29T08:00:00', + '2014-01-30T08:00:00', + '2014-01-31T08:00:00', + '2015-01-01T08:00:00' + ] + }); + + //Weekly Sunday, Monday, Tuesday with count=3 + testRRULE('FREQ=WEEKLY;COUNT=3;BYDAY=MO,SU,TU', { + dtStart: '2017-07-30', + byCount: true, + dates: [ + '2017-07-30', + '2017-07-31', + '2017-08-01' + ] + }); + + //Weekly Sunday, Wednesday with count=5 + testRRULE('FREQ=WEEKLY;COUNT=5;BYDAY=SU,WE', { + dtStart: '2017-04-23', + byCount: true, + dates: [ + '2017-04-23', + '2017-04-26', + '2017-04-30', + '2017-05-03', + '2017-05-07' + ] + }); + + //yearly, byMonth, byweekNo + /* TODO BYWEEKNO is not well supported + testRRULE('FREQ=YEARLY;BYMONTH=6,9;BYWEEKNO=23', { + dates: [ + '2015-06-08T08:00:00', + '2016-06-06T08:00:00', + '2017-06-05T08:00:00', + '2018-06-04T08:00:00' + ] + }); + + //yearly, byMonth, byweekNo negative + testRRULE('FREQ=YEARLY;BYMONTH=6,9;BYWEEKNO=-28', { + dates: [ + '2015-06-15T08:00:00', + '2016-06-06T08:00:00', + '2017-06-05T08:00:00', + '2018-06-04T08:00:00' + ] + }); + //yearly, negative byweekNo, negative bymonthday + testRRULE('FREQ=YEARLY;BYMONTHDAY=-27,-26,-25,-24,-23;BYWEEKNO=-28', { + dates: [ + '2016-06-06T08:00:00', + '2016-06-07T08:00:00', + '2016-06-08T08:00:00', + '2017-06-05T08:00:00', + '2017-06-06T08:00:00', + '2017-06-07T08:00:00', + '2017-06-08T08:00:00', + '2018-06-04T08:00:00', + '2018-06-05T08:00:00', + '2018-06-06T08:00:00', + '2018-06-07T08:00:00', + '2018-06-08T08:00:00' + ] + }); + + //yearly, byweekNo, bymonthday + testRRULE('FREQ=YEARLY;BYMONTHDAY=4,5,6,7,8;BYWEEKNO=23', { + dates: [ + '2015-06-08T08:00:00', + '2016-06-06T08:00:00', + '2016-06-07T08:00:00', + '2016-06-08T08:00:00', + '2017-06-05T08:00:00', + '2017-06-06T08:00:00', + '2017-06-07T08:00:00', + '2017-06-08T08:00:00', + '2018-06-04T08:00:00', + '2018-06-05T08:00:00', + '2018-06-06T08:00:00', + '2018-06-07T08:00:00', + '2018-06-08T08:00:00' + ] + }); + + //yearly, negative byweekNo, bymonthday + testRRULE('FREQ=YEARLY;BYMONTHDAY=4,5,6,7,8;BYWEEKNO=-28', { + dates: [ + '2016-06-06T08:00:00', + '2016-06-07T08:00:00', + '2016-06-08T08:00:00', + '2017-06-05T08:00:00', + '2017-06-06T08:00:00', + '2017-06-07T08:00:00', + '2017-06-08T08:00:00', + '2018-06-04T08:00:00', + '2018-06-05T08:00:00', + '2018-06-06T08:00:00', + '2018-06-07T08:00:00', + '2018-06-08T08:00:00' + ] + }); + */ + + //yearly, byDay,byMonthday + testRRULE('FREQ=YEARLY;BYDAY=+1MO;BYMONTHDAY=7', { + dtStart: '2015-01-01T08:00:00', + dates: [ + '2019-01-07T08:00:00' + ] + }); + + // Invalid recurrence rule. The first Monday can never fall later than the + // 7th. + testRRULE('FREQ=YEARLY;BYMONTHDAY=15,16,17,18,19,20,21;BYDAY=1MO', { + dtStart: '2015-01-01T08:00:00', + noInstance: true, + }); + + // Tycho brahe days - yearly, byYearDay with negative offsets + testRRULE('FREQ=YEARLY;BYYEARDAY=1,2,4,6,11,12,20,42,48,49,-306,-303,' + + '-293,-292,-266,-259,-258,-239,-228,-209,-168,-164,-134,-133,' + + '-113,-105,-87,-56,-44,-26,-21,-14', { + dtStart: '2015-01-01', + dates: [ + '2015-01-01', + '2015-01-02', + '2015-01-04', + '2015-01-06', + '2015-01-11', + '2015-01-12', + '2015-01-20', + '2015-02-11', + '2015-02-17', + '2015-02-18', + '2015-03-01', + '2015-03-04', + '2015-03-14', + '2015-03-15', + '2015-04-10', + '2015-04-17', + '2015-04-18', + '2015-05-07', + '2015-05-18', + '2015-06-06', + '2015-07-17', + '2015-07-21', + '2015-08-20', + '2015-08-21', + '2015-09-10', + '2015-09-18', + '2015-10-06', + '2015-11-06', + '2015-11-18', + '2015-12-06', + '2015-12-11', + '2015-12-18' + ] + }); + + // Leap year - yearly, byYearDay with negative offsets + testRRULE('FREQ=YEARLY;BYYEARDAY=-308,-307,-306', { + dtStart: '2012-01-01', + dates: [ + '2012-02-28', + '2012-02-29', + '2012-03-01', + ] + }); + + // Non-leap year - yearly, byYearDay with negative offsets + testRRULE('FREQ=YEARLY;BYYEARDAY=-307,-306,-305', { + dtStart: '2013-01-01', + dates: [ + '2013-02-28', + '2013-03-01', + '2013-03-02', + ] + }); + + // BYYEARDAY with positive and negative rules. + // Occurrences should be in chronological order. + testRRULE('FREQ=YEARLY;BYYEARDAY=359,-7', { + dtStart: '2024-01-01', + dates: [ + '2024-12-24', // 359 + '2024-12-25', // -7 + '2025-12-25', + '2026-12-25', + '2027-12-25', + '2028-12-24', + '2028-12-25', + ] + }); + + // BYYEARDAY with negative and positive rules. + // Occurrences should be in chronological order. + testRRULE('FREQ=YEARLY;BYYEARDAY=-7,359', { + dtStart: '2024-01-01', + dates: [ + '2024-12-24', // 359 + '2024-12-25', // -7 + '2025-12-25', + '2026-12-25', + '2027-12-25', + '2028-12-24', + '2028-12-25', + ] + }); + + /* + * Leap-year test for February 29th + * + * See https://github.com/kewisch/ical.js/issues/91 + * for details + * + * TODO: Uncomment when new recurrence iterator is ready + */ + + /* + testRRULE('FREQ=YEARLY;', { + dtStart: '2012-02-29T12:00:00', + dates: [ + '2012-02-29T12:00:00', + '2016-02-29T12:00:00' + ] + }); + */ + + // Multiple BYYEARDAYs, checks that we start on the right one. + testRRULE('FREQ=YEARLY;BYYEARDAY=73,146,219,292', { + dtStart: '2024-02-06T01:00:00', + dates: [ + '2024-03-13T01:00:00', + '2024-05-25T01:00:00', + '2024-08-06T01:00:00', + '2024-10-18T01:00:00', + '2025-03-14T01:00:00', + ] + }); + + testRRULE('FREQ=YEARLY;BYYEARDAY=73,146,219,292', { + dtStart: '2024-04-19T01:00:00', + dates: [ + '2024-05-25T01:00:00', + '2024-08-06T01:00:00', + '2024-10-18T01:00:00', + '2025-03-14T01:00:00', + '2025-05-26T01:00:00', + ] + }); + + testRRULE('FREQ=YEARLY;BYYEARDAY=73,146,219,292', { + dtStart: '2024-07-01T01:00:00', + dates: [ + '2024-08-06T01:00:00', + '2024-10-18T01:00:00', + '2025-03-14T01:00:00', + '2025-05-26T01:00:00', + '2025-08-07T01:00:00', + ] + }); + + testRRULE('FREQ=YEARLY;BYYEARDAY=73,146,219,292', { + dtStart: '2024-09-12T01:00:00', + dates: [ + '2024-10-18T01:00:00', + '2025-03-14T01:00:00', + '2025-05-26T01:00:00', + '2025-08-07T01:00:00', + '2025-10-19T01:00:00', + ] + }); + + testRRULE('FREQ=YEARLY;BYYEARDAY=73,146,219,292', { + dtStart: '2024-11-24T01:00:00', + dates: [ + '2025-03-14T01:00:00', + '2025-05-26T01:00:00', + '2025-08-07T01:00:00', + '2025-10-19T01:00:00', + '2026-03-14T01:00:00', + ] + }); + }); + }); +}); diff --git a/test/recur_test.js b/test/recur_test.js new file mode 100644 index 0000000..a511cec --- /dev/null +++ b/test/recur_test.js @@ -0,0 +1,701 @@ +suite('recur', function() { + suite('initialization', function() { + test('empty init', function() { + let recur = new ICAL.Recur(); + assert.equal(recur.interval, 1); + assert.equal(recur.wkst, ICAL.Time.MONDAY); + assert.isNull(recur.until); + assert.isNull(recur.count); + assert.isNull(recur.freq); + }); + }); + + suite('#iterator', function() { + function checkDate(data, last, dtstart) { + let name = JSON.stringify(data); + // XXX: better names + test('RULE: ' + name, function() { + let recur = new ICAL.Recur(data); + if (dtstart) { + dtstart = ICAL.Time.fromString(dtstart); + } else { + dtstart = ICAL.Time.epochTime.clone(); + } + let iter = recur.iterator(dtstart); + assert.equal(iter.next().toString(), last); + }); + } + + function checkThrow(data, expectedMessage, dtstart, stack) { + test(expectedMessage, function() { + let recur = new ICAL.Recur(data); + if (dtstart) { + dtstart = ICAL.Time.fromString(dtstart); + } else { + dtstart = ICAL.Time.epochTime.clone(); + } + assert.throws(function() { + recur.iterator(dtstart); + }, expectedMessage); + }); + } + + checkThrow({ + parts: { + BYYEARDAY: [3, 4, 5], + BYMONTH: [2] + } + }, 'Invalid BYYEARDAY rule'); + + checkThrow({ + parts: { + BYWEEKNO: [3], + BYMONTHDAY: [2] + } + }, 'BYWEEKNO does not fit to BYMONTHDAY'); + + checkThrow({ + freq: 'MONTHLY', + parts: { + BYWEEKNO: [30] + } + }, 'For MONTHLY recurrences neither BYYEARDAY nor BYWEEKNO may appear'); + + checkThrow({ + freq: 'WEEKLY', + parts: { + BYMONTHDAY: [20] + } + }, 'For WEEKLY recurrences neither BYMONTHDAY nor BYYEARDAY may appear'); + + checkThrow({ + freq: 'DAILY', + parts: { + BYYEARDAY: [200] + } + }, 'BYYEARDAY may only appear in YEARLY rules'); + + checkThrow({ + freq: 'MONTHLY', + parts: { + BYDAY: ['-6TH'] + } + }, 'Malformed values in BYDAY part', '1970-02-01T00:00:00Z'); + + checkDate({ + freq: 'SECONDLY', + parts: { + BYSECOND: ['2'], + BYMINUTE: ['2'], + BYHOUR: ['2'], + BYDAY: ['2'], + BYMONTHDAY: ['2'], + BYMONTH: ['2'], + BYSETPOS: ['2'] + } + }, '1970-01-01T00:00:00Z'); + + checkDate({ + freq: 'MINUTELY', + parts: { + BYSECOND: [2, 4, 6], + BYMINUTE: [1, 3, 5] + } + }, '1970-01-01T00:00:02Z'); + + checkDate({ + freq: 'YEARLY', + parts: { + BYSECOND: [1], + BYMINUTE: [2], + BYHOUR: [3], + BYMONTHDAY: [4], + BYMONTH: [5] + } + }, '1970-05-04T03:02:01Z'); + + checkDate({ + freq: 'WEEKLY', + parts: { + BYDAY: ['MO', 'TH', 'FR'] + } + }, '1970-01-01T00:00:00Z'); + + checkDate({ + freq: 'WEEKLY', + parts: { + BYDAY: ['MO', 'WE'] + } + }, '1970-01-05T00:00:00Z'); + + checkDate({ + freq: 'YEARLY', + parts: { + BYMONTH: [3] + } + }, '1970-03-05T00:00:00Z', '1970-01-05T00:00:00Z'); + + checkDate({ + freq: 'YEARLY', + parts: { + BYDAY: ['FR'], + BYMONTH: [12], + BYMONTHDAY: [1] + } + }, '1972-12-01T00:00:00Z'); + + checkDate({ + freq: 'MONTHLY', + parts: { + BYDAY: ['2MO'] + } + }, '1970-01-12T00:00:00Z'); + + checkDate({ + freq: 'MONTHLY', + parts: { + BYDAY: ['-3MO'] + } + }, '1970-01-12T00:00:00Z'); + + checkDate({ + freq: 'MONTHLY', + parts: { + BYDAY: ['WE'], + BYMONTHDAY: [1] + } + }, '1970-04-01T00:00:00Z'); + + // TODO bymonthday else part + // TODO check weekly without byday instances + 1 same wkday + }); + + test('#clone', function() { + let until = ICAL.Time.epochTime.clone(); + let a = new ICAL.Recur({ + interval: 2, + wkst: 3, + until: until, + count: 5, + freq: 'YEARLY' + }); + + let b = a.clone(); + + assert.equal(a.interval, b.interval); + assert.equal(a.wkst, b.wkst); + assert.equal(a.until.compare(b.until), 0); + assert.equal(a.count, b.count); + assert.equal(a.freq, b.freq); + + b.interval++; + b.wkst++; + b.until.day++; + b.count++; + b.freq = 'WEEKLY'; + + assert.notEqual(a.interval, b.interval); + assert.notEqual(a.wkst, b.wkst); + assert.notEqual(a.until.compare(b.until), 0); + assert.notEqual(a.count, b.count); + assert.notEqual(a.freq, b.freq); + }); + + suite('ICAL.Recur#toJSON', function() { + + test('round-trip', function() { + let recur = ICAL.Recur.fromString( + 'FREQ=MONTHLY;BYDAY=1SU,2MO;BYSETPOS=1;COUNT=10;UNTIL=20121001T090000' + ); + + let props = { + byday: ['1SU', '2MO'], + bysetpos: 1, + until: '2012-10-01T09:00:00', + freq: 'MONTHLY', + count: 10 + }; + + let result = recur.toJSON(); + assert.deepEqual(result, props); + + let fromJSON = new ICAL.Recur(result); + + assert.instanceOf(fromJSON.until, ICAL.Time); + + assert.hasProperties(fromJSON, { + freq: props.freq, + count: props.count, + }); + + assert.hasProperties(fromJSON.parts, { + BYDAY: props.byday, + BYSETPOS: [props.bysetpos] + }); + }); + }); + + test('components', function() { + let until = ICAL.Time.epochTime.clone(); + let a = new ICAL.Recur({ + interval: 2, + wkst: 3, + until: until, + count: 5, + freq: 'YEARLY', + parts: { + BYDAY: ['-1SU'] + } + }); + + assert.deepEqual(a.getComponent('BYDAY'), ['-1SU']); + assert.deepEqual(a.getComponent('BYWTF'), []); + + a.addComponent('BYDAY', '+2MO'); + assert.deepEqual(a.getComponent('byday'), ['-1SU', '+2MO']); + assert.deepEqual(a.getComponent('bywtf'), []); + + a.setComponent('BYDAY', ['WE', 'TH']); + assert.deepEqual(a.getComponent('BYDAY'), ['WE', 'TH']); + + a.addComponent('BYMONTHDAY', '31'); + assert.deepEqual(a.getComponent('bymonthday'), ['31']); + + let comp = a.getComponent('BYDAY'); + assert.equal(comp.length, 2); + }); + + suite('#fromString', function() { + + function verify(string, options) { + test('parse: "' + string + '"', function() { + let result = ICAL.Recur.fromString(string); + // HACK for until validation + if (options.until) { + let until = options.until; + delete options.until; + assert.hasProperties(result.until, until); + } + assert.hasProperties(result, options); + }); + } + + function verifyFail(string, errorParam) { + test('invalid input "' + string + '"', function() { + assert.throws(function() { + ICAL.Recur.fromString(string); + }, errorParam); + }); + } + + verifyFail('FREQ=FOOBAR', /invalid frequency/); + verify('FREQ=YEARLY;BYYEARDAY=300,301,-1', { + freq: 'YEARLY', + parts: { BYYEARDAY: [300, 301, -1] } + }); + + verifyFail('BYYEARDAY=367', /BYYEARDAY/); + verifyFail('BYYEARDAY=-367', /BYYEARDAY/); + + verify('FREQ=MONTHLY;BYMONTHDAY=+3', { + freq: 'MONTHLY', + parts: { BYMONTHDAY: [3] } + }); + + verify('FREQ=MONTHLY;BYMONTHDAY=-3', { + freq: 'MONTHLY', + parts: { BYMONTHDAY: [-3] } + }); + + verify('BYSECOND=10;BYMINUTE=11;BYHOUR=12;BYWEEKNO=53;BYSETPOS=30', { + parts: { + BYSECOND: [10], + BYMINUTE: [11], + BYHOUR: [12], + BYWEEKNO: [53], + BYSETPOS: [30] + } + }); + + verify('FREQ=DAILY;INTERVAL=3;COUNT=10;', { + freq: 'DAILY', + count: 10, + interval: 3 + }); + + verify('BYDAY=1SU,MO,TU,-53MO,13FR', { + parts: { + BYDAY: ['1SU', 'MO', 'TU', '-53MO', '13FR'] + } + }); + + verifyFail('BYDAY=ZA,FO1', /invalid BYDAY/); + + verify('UNTIL=20121012T101507', { + until: { + year: 2012, + month: 10, + day: 12, + hour: 10, + minute: 15, + second: 7 + } + }); + + verify('WKST=SU', { + wkst: 1 + }); + + verifyFail('WKST=ofo', /invalid WKST/); + + // Zero or negative interval should be accepted as interval=1 + verify('INTERVAL=0', { + interval: 1 + }); + verify('INTERVAL=-1', { + interval: 1 + }); + }); + + suite('#fromData', function() { + + function verify(data, options) { + test('parse: "' + JSON.stringify(data) + '"', function() { + assert.hasProperties(ICAL.Recur.fromData(data), options); + }); + } + + function verifyFail(data) { + test('invalid input "' + JSON.stringify(data) + '"', function() { + assert.throws(function() { + ICAL.Recur.fromString(data); + }); + }); + } + + verify({}, {}); + + // INTERVAL checks + verify({ interval: 1 }, { interval: 1 }); + verify({ count: 1 }, { count: 1 }); + verify({ interval: '1' }, { interval: 1 }); + verifyFail({ interval: 'NaN' }); + }); + + suite('#getNextOccurrence', function() { + test('basic test', function() { + let rec = ICAL.Recur.fromString('FREQ=DAILY;INTERVAL=2'); + let dtstart = ICAL.Time.epochTime.clone(); + let recId = dtstart.clone(); + recId.day += 20; + + let next = rec.getNextOccurrence(dtstart, recId); + assert.deepEqual(next.toJSON(), { + year: 1970, + month: 1, + day: 23, + hour: 0, + minute: 0, + second: 0, + isDate: false, + timezone: 'UTC' + }); + }); + + test('no next occurrence', function() { + let rec = ICAL.Recur.fromString('FREQ=DAILY;INTERVAL=2;UNTIL=19700103T000000Z'); + let dtstart = ICAL.Time.epochTime.clone(); + let recId = dtstart.clone(); + recId.day += 20; + + assert.isNull(rec.getNextOccurrence(dtstart, recId)); + }); + }); + + suite('recur data types', function() { + test('invalid freq', function() { + assert.throws(function() { + ICAL.Recur.fromString("FREQ=123"); + }, /invalid frequency/); + }); + + test('invalid wkst', function() { + assert.throws(function() { + ICAL.Recur.fromString("FREQ=WEEKLY;WKST=DUNNO"); + }, /invalid WKST value/); + }); + + test('invalid count', function() { + assert.throws(function() { + ICAL.Recur.fromString("FREQ=WEEKLY;COUNT=MAYBE10"); + }, /Could not extract integer from/); + }); + + test('invalid interval', function() { + assert.throws(function() { + ICAL.Recur.fromString("FREQ=WEEKLY;INTERVAL=ADAGIO"); + }, /Could not extract integer from/); + }); + + test('invalid numeric byday', function() { + assert.throws(function() { + ICAL.Recur.fromString("FREQ=WEEKLY;BYDAY=1,2,3"); + }, /invalid BYDAY value/); + }); + + test('extra structured recur values', function() { + let rec = ICAL.Recur.fromString("RSCALE=ISLAMIC-CIVIL;FREQ=YEARLY;BYMONTH=9"); + assert.equal(rec.rscale, "ISLAMIC-CIVIL"); + }); + + test('single BYxxx value from string', function() { + let rec = ICAL.Recur.fromString("FREQ=MINUTELY;BYSECOND=5"); + let comp = rec.getComponent("bysecond"); + assert.equal(comp.length, 1); + assert.equal(comp[0], 5); + }); + + test('single BYxxx value from jCal', function() { + let prop = new ICAL.Property("rrule"); + prop.setValue({ freq: "minutely", bysecond: 5 }); + let val = prop.getFirstValue(); + + let comp = val.getComponent("bysecond"); + assert.equal(comp.length, 1); + assert.equal(comp[0], 5); + }); + + test('multiple BYxxx values from string', function() { + let rec = ICAL.Recur.fromString("FREQ=YEARLY;BYYEARDAY=20,30,40"); + let comp = rec.getComponent("byyearday"); + assert.deepEqual(comp, [20, 30, 40]); + }); + + test('multiple BYxxx values from jCal', function() { + let prop = new ICAL.Property("rrule"); + prop.setValue({ freq: "yearly", byyearday: [20, 30, 40] }); + let val = prop.getFirstValue(); + + let comp = val.getComponent("byyearday"); + assert.deepEqual(comp, [20, 30, 40]); + }); + + test('can be saved to a property that will be serialized correctly', function() { + let icalString = 'FREQ=WEEKLY;UNTIL=19700103T000000Z;WKST=SU;BYDAY=TU,TH'; + let recur = ICAL.Recur.fromString(icalString); + let prop = new ICAL.Property('rrule'); + prop.setValue(recur); + assert.equal(prop.toICALString(), 'RRULE:FREQ=WEEKLY;BYDAY=TU,TH;UNTIL=19700103T000000Z;WKST=SU'); + }); + }); + + suite('#toString', function() { + test('round trip', function() { + let until = ICAL.Time.epochTime.clone(); + let data = { + interval: 2, + wkst: 3, + until: until, + count: 5, + freq: 'YEARLY', + parts: { + 'BYDAY': 'TU', + 'BYMONTH': '1' + } + }; + + let a = new ICAL.Recur(data); + let output = a.toString(); + let b = ICAL.Recur.fromString(output); + + assert.ok(a.toString(), 'outputs'); + + assert.include(output, ';UNTIL=19700101T000000Z'); + // wkst 3 == TU see DOW_MAP + assert.include(output, 'WKST=TU'); + assert.include(output, 'COUNT=5'); + assert.include(output, 'INTERVAL=2'); + assert.include(output, 'FREQ=YEARLY'); + assert.include(output, 'BYMONTH=1'); + assert.include(output, 'BYDAY=TU'); + + assert.equal(a.toString(), b.toString(), 'roundtrip equality'); + }); + test('not all props', function() { + let data = { + freq: 'YEARLY', + }; + + let a = new ICAL.Recur(data); + assert.equal(a.toString(), 'FREQ=YEARLY'); + }); + }); + + suite('ICAL.Recur#icalDayToNumericDay', function() { + let expectedDayMap = { + 'SU': ICAL.Time.SUNDAY, + 'MO': ICAL.Time.MONDAY, + 'TU': ICAL.Time.TUESDAY, + 'WE': ICAL.Time.WEDNESDAY, + 'TH': ICAL.Time.THURSDAY, + 'FR': ICAL.Time.FRIDAY, + 'SA': ICAL.Time.SATURDAY + }; + + Object.entries(expectedDayMap).forEach(([icalDay, numericDay]) => { + test(icalDay + ' to constant', function() { + assert.equal( + ICAL.Recur.icalDayToNumericDay(icalDay), + numericDay + ); + }); + }); + + let expectedWithWkst = [ + //day, wkst, expected + ['SU', ICAL.Time.SUNDAY, 1], + ['MO', ICAL.Time.SUNDAY, 2], + ['TU', ICAL.Time.SUNDAY, 3], + ['WE', ICAL.Time.SUNDAY, 4], + ['TH', ICAL.Time.SUNDAY, 5], + ['FR', ICAL.Time.SUNDAY, 6], + ['SA', ICAL.Time.SUNDAY, 7], + ['SU', ICAL.Time.MONDAY, 7], + ['MO', ICAL.Time.MONDAY, 1], + ['TU', ICAL.Time.MONDAY, 2], + ['WE', ICAL.Time.MONDAY, 3], + ['TH', ICAL.Time.MONDAY, 4], + ['FR', ICAL.Time.MONDAY, 5], + ['SA', ICAL.Time.MONDAY, 6], + ['SU', ICAL.Time.TUESDAY, 6], + ['MO', ICAL.Time.TUESDAY, 7], + ['TU', ICAL.Time.TUESDAY, 1], + ['WE', ICAL.Time.TUESDAY, 2], + ['TH', ICAL.Time.TUESDAY, 3], + ['FR', ICAL.Time.TUESDAY, 4], + ['SA', ICAL.Time.TUESDAY, 5], + ['SU', ICAL.Time.WEDNESDAY, 5], + ['MO', ICAL.Time.WEDNESDAY, 6], + ['TU', ICAL.Time.WEDNESDAY, 7], + ['WE', ICAL.Time.WEDNESDAY, 1], + ['TH', ICAL.Time.WEDNESDAY, 2], + ['FR', ICAL.Time.WEDNESDAY, 3], + ['SA', ICAL.Time.WEDNESDAY, 4], + ['SU', ICAL.Time.THURSDAY, 4], + ['MO', ICAL.Time.THURSDAY, 5], + ['TU', ICAL.Time.THURSDAY, 6], + ['WE', ICAL.Time.THURSDAY, 7], + ['TH', ICAL.Time.THURSDAY, 1], + ['FR', ICAL.Time.THURSDAY, 2], + ['SA', ICAL.Time.THURSDAY, 3], + ['SU', ICAL.Time.FRIDAY, 3], + ['MO', ICAL.Time.FRIDAY, 4], + ['TU', ICAL.Time.FRIDAY, 5], + ['WE', ICAL.Time.FRIDAY, 6], + ['TH', ICAL.Time.FRIDAY, 7], + ['FR', ICAL.Time.FRIDAY, 1], + ['SA', ICAL.Time.FRIDAY, 2], + ['SU', ICAL.Time.SATURDAY, 2], + ['MO', ICAL.Time.SATURDAY, 3], + ['TU', ICAL.Time.SATURDAY, 4], + ['WE', ICAL.Time.SATURDAY, 5], + ['TH', ICAL.Time.SATURDAY, 6], + ['FR', ICAL.Time.SATURDAY, 7], + ['SA', ICAL.Time.SATURDAY, 1] + ]; + + expectedWithWkst.forEach(([day, wkst, expected]) => { + test(day + ' to constant, wkst = ' + wkst, function() { + assert.equal( + ICAL.Recur.icalDayToNumericDay(day, wkst), + expected + ); + }); + }); + }); + + suite('ICAL.Recur#numericDayToIcalDay', function() { + let expected = { + [ICAL.Time.SUNDAY]: 'SU', + [ICAL.Time.MONDAY]: 'MO', + [ICAL.Time.TUESDAY]: 'TU', + [ICAL.Time.WEDNESDAY]: 'WE', + [ICAL.Time.THURSDAY]: 'TH', + [ICAL.Time.FRIDAY]: 'FR', + [ICAL.Time.SATURDAY]: 'SA' + }; + + Object.entries(expected).forEach(([numericDay, icalDay]) => { + test(numericDay + ' to ' + icalDay, function() { + assert.equal( + ICAL.Recur.numericDayToIcalDay(+numericDay), + icalDay + ); + }); + }); + }); + + let expectedWithWkst = [ + //expectedDay, wkst, numericDay + ['SU', ICAL.Time.SUNDAY, 1], + ['MO', ICAL.Time.SUNDAY, 2], + ['TU', ICAL.Time.SUNDAY, 3], + ['WE', ICAL.Time.SUNDAY, 4], + ['TH', ICAL.Time.SUNDAY, 5], + ['FR', ICAL.Time.SUNDAY, 6], + ['SA', ICAL.Time.SUNDAY, 7], + ['SU', ICAL.Time.MONDAY, 7], + ['MO', ICAL.Time.MONDAY, 1], + ['TU', ICAL.Time.MONDAY, 2], + ['WE', ICAL.Time.MONDAY, 3], + ['TH', ICAL.Time.MONDAY, 4], + ['FR', ICAL.Time.MONDAY, 5], + ['SA', ICAL.Time.MONDAY, 6], + ['SU', ICAL.Time.TUESDAY, 6], + ['MO', ICAL.Time.TUESDAY, 7], + ['TU', ICAL.Time.TUESDAY, 1], + ['WE', ICAL.Time.TUESDAY, 2], + ['TH', ICAL.Time.TUESDAY, 3], + ['FR', ICAL.Time.TUESDAY, 4], + ['SA', ICAL.Time.TUESDAY, 5], + ['SU', ICAL.Time.WEDNESDAY, 5], + ['MO', ICAL.Time.WEDNESDAY, 6], + ['TU', ICAL.Time.WEDNESDAY, 7], + ['WE', ICAL.Time.WEDNESDAY, 1], + ['TH', ICAL.Time.WEDNESDAY, 2], + ['FR', ICAL.Time.WEDNESDAY, 3], + ['SA', ICAL.Time.WEDNESDAY, 4], + ['SU', ICAL.Time.THURSDAY, 4], + ['MO', ICAL.Time.THURSDAY, 5], + ['TU', ICAL.Time.THURSDAY, 6], + ['WE', ICAL.Time.THURSDAY, 7], + ['TH', ICAL.Time.THURSDAY, 1], + ['FR', ICAL.Time.THURSDAY, 2], + ['SA', ICAL.Time.THURSDAY, 3], + ['SU', ICAL.Time.FRIDAY, 3], + ['MO', ICAL.Time.FRIDAY, 4], + ['TU', ICAL.Time.FRIDAY, 5], + ['WE', ICAL.Time.FRIDAY, 6], + ['TH', ICAL.Time.FRIDAY, 7], + ['FR', ICAL.Time.FRIDAY, 1], + ['SA', ICAL.Time.FRIDAY, 2], + ['SU', ICAL.Time.SATURDAY, 2], + ['MO', ICAL.Time.SATURDAY, 3], + ['TU', ICAL.Time.SATURDAY, 4], + ['WE', ICAL.Time.SATURDAY, 5], + ['TH', ICAL.Time.SATURDAY, 6], + ['FR', ICAL.Time.SATURDAY, 7], + ['SA', ICAL.Time.SATURDAY, 1] + ]; + + for (let i = 0; i< expectedWithWkst.length; i++) { + (function(list) { + test(list[2] + ' to string, wkst = ' + list[1], function() { + assert.equal( + ICAL.Recur.numericDayToIcalDay(list[2], list[1]), + list[0] + ); + }); + }(expectedWithWkst[i])); + } +}); diff --git a/test/stringify_test.js b/test/stringify_test.js new file mode 100644 index 0000000..48a6714 --- /dev/null +++ b/test/stringify_test.js @@ -0,0 +1,230 @@ +suite('ICAL.stringify', function() { + + suite('round trip tests', function() { + let root = 'samples/'; + let list = [ + 'minimal', + 'blank_line_end', + 'forced_types', + 'parserv2', + 'utc_negative_zero' + ]; + + list.forEach(function(path) { + suite(path.replace('_', ' '), function() { + let input; + + // fetch ical + setup(async function() { + input = await testSupport.load(root + path + '.ics'); + }); + + function jsonEqual(actual, expected) { + assert.deepEqual( + actual, + expected, + 'hint use: ' + + 'http://tlrobinson.net/projects/javascript-fun/jsondiff/\n\n' + + '\nexpected:\n\n' + + JSON.stringify(actual, null, 2) + + '\n\n to equal:\n\n ' + + JSON.stringify(expected, null, 2) + '\n\n' + ); + } + + test('round-trip', function() { + let parsed = ICAL.parse(input); + let ical = ICAL.stringify(parsed); + + // NOTE: this is not an absolute test that serialization + // works as our parser should be error tolerant and + // it is remotely possible that we consistently produce + // ICAL that only we can parse. + jsonEqual( + ICAL.parse(ical), + parsed + ); + }); + + }); + }); + }); + + suite('stringify property', function() { + test('no explicit default set', function() { + let subject = new ICAL.Property('tz', new ICAL.Component('vcard')); + subject.setValue(ICAL.UtcOffset.fromString('+0500')); + + let ical = ICAL.stringify.property(subject.toJSON()); + assert.equal(ical, 'TZ;VALUE=UTC-OFFSET:+0500'); + }); + test('custom property with no default type', function() { + ICAL.design.defaultSet.property.custom = {}; + let subject = new ICAL.Property('custom'); + subject.setValue('unescaped, right?'); + assert.equal(subject.toICALString(), 'CUSTOM:unescaped, right?'); + + subject.resetType('integer'); + subject.setValue(123); + assert.equal(subject.toICALString(), 'CUSTOM;VALUE=INTEGER:123'); + + delete ICAL.design.defaultSet.property.custom; + }); + + test('custom property not using default type', function() { + ICAL.design.defaultSet.property.custom = { defaultType: 'text' }; + let subject = new ICAL.Property('custom'); + subject.resetType('integer'); + subject.setValue(123); + assert.equal(subject.toICALString(), 'CUSTOM;VALUE=INTEGER:123'); + delete ICAL.design.defaultSet.property.custom; + }); + + test('property with multiple parameter values', function() { + ICAL.design.defaultSet.property.custom = { defaultType: 'text' }; + ICAL.design.defaultSet.param.type = { multiValue: ',' }; + let subject = new ICAL.Property('custom'); + subject.setParameter('type', ['ABC', 'XYZ']); + subject.setValue('some value'); + assert.equal(subject.toICALString(), 'CUSTOM;TYPE=ABC,XYZ:some value'); + delete ICAL.design.defaultSet.property.custom; + delete ICAL.design.defaultSet.param.type; + }); + + test('property with multiple parameter values which must be escaped', function() { + ICAL.design.defaultSet.property.custom = { defaultType: 'text' }; + ICAL.design.defaultSet.param.type = { multiValue: ',' }; + let subject = new ICAL.Property('custom'); + subject.setParameter('type', ['ABC', '--"XYZ"--']); + subject.setValue('some value'); + assert.equal(subject.toICALString(), "CUSTOM;TYPE=ABC,--^'XYZ^'--:some value"); + delete ICAL.design.defaultSet.property.custom; + delete ICAL.design.defaultSet.param.type; + }); + + test('property with multiple parameter values with enabled quoting', function() { + ICAL.design.defaultSet.property.custom = { defaultType: 'text' }; + ICAL.design.defaultSet.param.type = { multiValue: ',', multiValueSeparateDQuote: true }; + let subject = new ICAL.Property('custom'); + subject.setParameter('type', ['ABC', 'XYZ']); + subject.setValue('some value'); + assert.equal(subject.toICALString(), 'CUSTOM;TYPE="ABC","XYZ":some value'); + delete ICAL.design.defaultSet.property.custom; + delete ICAL.design.defaultSet.param.type; + }); + + test('stringify property value containing "escaped" semicolons, commas, colons', function() { + let subject = new ICAL.Property('attendee'); + subject.setParameter('cn', 'X\\:'); + subject.setValue('mailto:id'); + assert.equal(subject.toICALString(), 'ATTENDEE;CN="X\\:":mailto:id'); + }); + + test('rfc6868 roundtrip', function() { + let subject = new ICAL.Property('attendee'); + let input = "caret ^ dquote \" newline \n end"; + let expected = 'ATTENDEE;CN=caret ^^ dquote ^\' newline ^n end:mailto:id'; + subject.setParameter('cn', input); + subject.setValue('mailto:id'); + assert.equal(subject.toICALString(), expected); + assert.equal(ICAL.parse.property(expected)[1].cn, input); + }); + + test('roundtrip for property with multiple parameters', function() { + ICAL.design.defaultSet.property.custom = { defaultType: 'text' }; + ICAL.design.defaultSet.param.type = { multiValue: ',', multiValueSeparateDQuote: true }; + let subject = new ICAL.Property('custom'); + subject.setParameter('type', ['ABC', '--"123"--']); + subject.setValue('some value'); + assert.lengthOf(ICAL.parse.property(subject.toICALString())[1].type, 2); + assert.include(ICAL.parse.property(subject.toICALString())[1].type, 'ABC'); + assert.include(ICAL.parse.property(subject.toICALString())[1].type, '--"123"--'); + delete ICAL.design.defaultSet.property.custom; + delete ICAL.design.defaultSet.param.type; + }); + + test('folding', function() { + let oldLength = ICAL.foldLength; + let subject = new ICAL.Property("description"); + let N = ICAL.newLineChar + " "; + subject.setValue('foobar'); + + ICAL.foldLength = 19; + assert.equal(subject.toICALString(), "DESCRIPTION:foobar"); + assert.equal(ICAL.stringify.property(subject.toJSON(), ICAL.design.icalendar, false), "DESCRIPTION:foobar"); + assert.equal(ICAL.stringify.property(subject.toJSON(), ICAL.design.icalendar, true), "DESCRIPTION:foobar"); + + ICAL.foldLength = 15; + assert.equal(subject.toICALString(), "DESCRIPTION:foobar"); + assert.equal(ICAL.stringify.property(subject.toJSON(), ICAL.design.icalendar, false), "DESCRIPTION:foo" + N + "bar"); + assert.equal(ICAL.stringify.property(subject.toJSON(), ICAL.design.icalendar, true), "DESCRIPTION:foobar"); + + let utf16_muscle = '\uD83D\uDCAA'; //in UTF-8 this is F0 DF 92 AA. If space/new line is inserted between the surrogates, then the JS Engine substitutes each stand-alone surrogate with REPLACEMENT CHARACTER 0xEF 0xBF 0xBD + subject.setValue(utf16_muscle); + assert.equal(ICAL.stringify.property(subject.toJSON(), ICAL.design.icalendar, false), "DESCRIPTION:" + N + utf16_muscle);//verify new line is after ':', as otherwise the whole line is longer than ICAL.foldLength + subject.setValue('aa' + utf16_muscle + utf16_muscle + 'a' + utf16_muscle + utf16_muscle); + assert.equal(ICAL.stringify.property(subject.toJSON(), ICAL.design.icalendar, false), "DESCRIPTION:aa" + N + utf16_muscle + utf16_muscle + 'a' + utf16_muscle + N + utf16_muscle);//verify that the utf16_muscle is moved as whole to a new line as it is 4 UTF-8 bytes + + ICAL.foldLength = oldLength; + }); + + test('property groups', function() { + // Make sure the GROUP param is stripped + let subject = ["fn", { "group": "bff" }, "text", "coffee"]; + assert.equal(ICAL.stringify.property(subject, ICAL.design.vcard, false), "BFF.FN:coffee"); + }); + }); + + suite('stringify component', function() { + test('minimal jcal', function() { + let subject = ["vcalendar", [["version", {}, "text", "2.0"]], [["vevent", [], []]]]; + let expected = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nBEGIN:VEVENT\r\nEND:VEVENT\r\nEND:VCALENDAR"; + + assert.equal(ICAL.stringify.component(subject), expected); + }); + + test('minimal jcard', function() { + // related to issue #266 + let subject = ["vcard", [["version", {}, "text", "4.0"]]]; + let expected = "BEGIN:VCARD\r\nVERSION:4.0\r\nEND:VCARD"; + + assert.equal(ICAL.stringify.component(subject), expected); + }); + + test('minimal jcard with empty subcomponent', function() { + let subject = ["vcard", [["version", {}, "text", "4.0"]], []]; + let expected = "BEGIN:VCARD\r\nVERSION:4.0\r\nEND:VCARD"; + + assert.equal(ICAL.stringify.component(subject), expected); + }); + + test('structured values', function() { + let subject = [ + "vcard", + [ + [ + "adr", + {}, + "text", + [ + "one", + "two", + "three\n\n", + "four\nfour\n", + [ + "five", + "five\n\n", + "five\nfive\n" + ], + "six", + "seven" + ] + ] + ] + ]; + let expected = "BEGIN:VCARD\r\nADR:one;two;three\\n\\n;four\\nfour\\n;five,five\\n\\n,five\\nfive\\n;six;seven\r\nEND:VCARD"; + + assert.equal(ICAL.stringify.component(subject), expected); + }); + }); +}); diff --git a/test/support/helper.js b/test/support/helper.js new file mode 100644 index 0000000..806ef33 --- /dev/null +++ b/test/support/helper.js @@ -0,0 +1,216 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */ + +let crossGlobal = typeof(window) === 'undefined' ? global : window; +let testSupport = crossGlobal.testSupport = { + isNode: (typeof(global) !== 'undefined'), + isKarma: (typeof(window) !== 'undefined' && typeof window.__karma__ !== 'undefined') +}; + +if (testSupport.isKarma) { + // Need to do this before the first await, browser/karma won't wait on top level await + window.__karma__.loaded = function() {}; +} + +/* eslint-disable no-var, no-redeclare */ +if (testSupport.isNode) { + var ICAL = (await import("../../lib/ical/module.js")).default; + var chai = await import("chai"); + var Benchmark = (await import("benchmark")).default; + var { URL } = await import("url"); + var { readFile, readdir } = (await import('fs/promises')); +} else { + var ICAL = (await import("/base/lib/ical/module.js")).default; + var chai = window.chai; +} +/* eslint-enable no-var, no-redeclare*/ + +crossGlobal.ICAL = ICAL; +chai.config.includeStack = true; +crossGlobal.assert = chai.assert; +crossGlobal.assert.hasProperties = function chai_hasProperties(given, props, msg) { + msg = (typeof(msg) === 'undefined') ? '' : msg + ': '; + + if (props instanceof Array) { + props.forEach(function(prop) { + crossGlobal.assert.ok( + (prop in given), + msg + 'given should have "' + prop + '" property' + ); + }); + } else { + for (let key in props) { + crossGlobal.assert.deepEqual( + given[key], + props[key], + msg + ' property equality for (' + key + ') ' + ); + } + } +}; + +/** + * Registers a given timezone from samples with the timezone service. + * + * @param {String} zoneName like "America/Los_Angeles". + */ +testSupport.registerTimezone = async function(zoneName) { + function register(icsData) { + let parsed = ICAL.parse(icsData); + let calendar = new ICAL.Component(parsed); + let vtimezone = calendar.getFirstSubcomponent('vtimezone'); + + ICAL.TimezoneService.register(vtimezone); + } + + if (!this._timezones) { + this._timezones = Object.create(null); + } + + let ics = this._timezones[zoneName]; + + if (ics) { + return register(ics); + } else { + let path = 'samples/timezones/' + zoneName + '.ics'; + let data = await testSupport.load(path); + let zone = register(data); + this._timezones[zone] = data; + return zone; + } +}; + +/** + * Registers a timezones for a given suite of tests. Uses suiteSetup to stage + * and will use suiteTeardown to purge all timezones for clean tests.. + * + * Please note that you should only call this once per suite, otherwise the + * teardown function will reset the service while the parent suite will still + * need them. + */ +testSupport.useTimezones = function(zones) { + let allZones = Array.prototype.slice.call(arguments); + + suiteTeardown(function() { + // to ensure clean tests + ICAL.TimezoneService.reset(); + }); + + suiteSetup(async function() { + // By default, Z/UTC/GMT are already registered + if (ICAL.TimezoneService.count > 3) { + throw new Error("Can only register zones once"); + } + + await Promise.all(allZones.map(zone => testSupport.registerTimezone(zone))); + }); +}; + +/** + * @param {String} path relative to root (/) of project. + */ +testSupport.load = async function(path) { + if (testSupport.isNode) { + let root = new URL('../../' + path, import.meta.url).pathname; + return readFile(root, 'utf8'); + } else { + let response = await fetch("/base/" + path); + if (response.status == 200) { + let text = await response.text(); + return text; + } else { + let err = new Error('file not found or other error', response); + throw err; + } + } +}; + +testSupport.loadSample = async function(file) { + return testSupport.load('samples/' + file); +}; + +let icalPerf = {}; +function perfTestDefine(scope, done) { + this.timeout(0); + let benchSuite = new Benchmark.Suite(); + let currentTest = this.test; + benchSuite.add("latest", scope.bind(this)); + Object.entries(icalPerf).forEach(([key, ical]) => { + benchSuite.add(key, () => { + let lastGlobal = crossGlobal.ICAL; + crossGlobal.ICAL = ical; + scope.call(this); + crossGlobal.ICAL = lastGlobal; + }); + }); + + currentTest._benchCycle = []; + + benchSuite.on('cycle', function(event) { + currentTest._benchCycle.push(String(event.target)); + }); + + benchSuite.on('complete', function(event) { + currentTest._benchFastest = this.filter('fastest').map('name'); + done(event.target.error); + }); + + benchSuite.run(); +} + +crossGlobal.perfTest = function(name, scope) { + test(name, function(done) { + perfTestDefine.call(this, scope, done); + }); +}; +crossGlobal.perfTest.only = function(name, scope) { + test.only(name, function(done) { + perfTestDefine.call(this, scope, done); + }); +}; +crossGlobal.perfTest.skip = function(name, scope) { + test.skip(name, function(done) { + perfTestDefine.call(this, scope, done); + }); +}; + +if (!testSupport.isNode) { + console.log("KARMA"); + try { + for (let file in window.__karma__.files) { + if (Object.hasOwn(window.__karma__.files, file)) { + if (/_test\.js$/.test(file)) { + await import(file); + } + } + } + + window.__karma__.start(); + } catch (e) { + window.__karma__.error(e.toString()); + } +} + +export const mochaHooks = { + async beforeAll() { + let benchmark = new URL('../../tools/benchmark', import.meta.url).pathname; + let files = await readdir(benchmark); + for (let file of files) { + let match = file.match(/^ical_(\w+).c?js$/); + if (match) { + try { + let module = await import("../../tools/benchmark/" + file); + if (module.default) { + icalPerf[match[1]] = module.default; + } else { + console.error(`Error loading tools/benchmark/${file}, skipping for performance tests: Missing default export`); + } + } catch (e) { + console.error(`Error loading tools/benchmark/${file}, skipping for performance tests: ${e}`); + } + } + } + } +}; diff --git a/test/support/perfReporter.cjs b/test/support/perfReporter.cjs new file mode 100644 index 0000000..56bf7b0 --- /dev/null +++ b/test/support/perfReporter.cjs @@ -0,0 +1,68 @@ +'use strict'; + +const Mocha = require('mocha'); +const { + EVENT_RUN_BEGIN, + EVENT_RUN_END, + EVENT_TEST_FAIL, + EVENT_TEST_PASS, + EVENT_SUITE_BEGIN, + EVENT_SUITE_END +} = Mocha.Runner.constants; + +const { Base } = Mocha.reporters; +const color = Base.color; + +// this reporter outputs test results, indenting two spaces per suite +class MyReporter extends Base { + constructor(runner, options) { + super(runner, options); + this._indents = 0; + this._n = 0; + + runner + .once(EVENT_RUN_BEGIN, () => { + Base.consoleLog(); + }) + .on(EVENT_SUITE_BEGIN, (suite) => { + this._indents++; + Base.consoleLog(color('suite', '%s%s'), this.indent(), suite.title); + }) + .on(EVENT_SUITE_END, () => { + this._indents--; + if (this._indents === 1) { + Base.consoleLog(); + } + }) + .on(EVENT_TEST_PASS, test => { + // Test#fullTitle() returns the suite name(s) + // prepended to the test title + let fmt = + this.indent() + + color('checkmark', ' ' + Base.symbols.ok) + + color('pass', ' %s') + + ' (fastest: %s)'; + Base.consoleLog(fmt, test.fullTitle(), test._benchFastest.join(",")); + this._indents += 2; + Base.consoleLog(this.indent() + test._benchCycle.join("\n" + this.indent())); + this._indents -= 2; + }) + .on(EVENT_TEST_FAIL, (test) => { + Base.consoleLog(this.indent() + color('fail', ' %d) %s'), ++this._n, test.title); + }) + .once(EVENT_RUN_END, this.epilogue.bind(this)); + } + + indent() { + return Array(this._indents).join(' '); + } + + increaseIndent() { + } + + decreaseIndent() { + this._indents--; + } +} + +module.exports = MyReporter; diff --git a/test/time_test.js b/test/time_test.js new file mode 100644 index 0000000..3fb59ea --- /dev/null +++ b/test/time_test.js @@ -0,0 +1,1816 @@ +suite('icaltime', function() { + let Time = ICAL.Time; + let Timezone = ICAL.Timezone; + + test('round trip', function() { + let f = new Time({ + second: 1, + minute: 2, + hour: 3, + day: 4, + month: 5, + year: 6007 + }); + + let g = f.clone(); + g.fromJSDate(f.toJSDate()); + assert.equal(f.toString(), g.toString()); + // TODO also check UTC dates + + g.reset(); + assert.equal(g, Time.epochTime.toString()); + }); + + suite('initialize', function() { + let icsData; + suiteSetup(async function() { + icsData = await testSupport.loadSample('timezones/America/New_York.ics'); + }); + + test('with timezone', function() { + let parsed = ICAL.parse(icsData); + let vcalendar = new ICAL.Component(parsed); + let vtimezone = vcalendar.getFirstSubcomponent('vtimezone'); + let tzid = vtimezone.getFirstPropertyValue('tzid'); + + ICAL.TimezoneService.register(vtimezone); + + // utc -5 + let time = new ICAL.Time({ + year: 2012, + month: 1, + day: 1, + hour: 10, + timezone: tzid + }); + + // -5 + assert.equal(time.utcOffset() / 3600, -5); + + assert.equal( + time.toUnixTime(), + Date.UTC(2012, 0, 1, 15) / 1000 + ); + + ICAL.TimezoneService.reset(); + }); + }); + + suite('.icaltime', function() { + function verify(time, type) { + test('convert time ' + JSON.stringify(time), function() { + assert.equal( + (new ICAL.Time(time)).icaltype, + type + ); + }); + } + + verify({ year: 2013, month: 1, day: 1 }, 'date'); + verify( + { year: 2013, month: 1, day: 1, hour: 3, isDate: true }, + 'date' + ); + + verify( + { year: 2013, month: 1, day: 1, hour: 22 }, + 'date-time' + ); + + verify( + { year: 2013, isDate: false }, + 'date-time' + ); + + test('converting types during runtime', function() { + let time = new ICAL.Time({ + year: 2013, isDate: false + }); + + time.isDate = true; + assert.equal(time.icaltype, 'date'); + }); + }); + + suite('setters', function() { + let subject; + + setup(function() { + subject = new ICAL.Time({ + year: 2012, + month: 12, + day: 31, + // needed otherwise this object + // is treated as a date rather then + // date-time and hour/minute/second will + // not be normalized/adjusted. + hour: 0 + }); + + subject.debug = true; + }); + + function movedToNextYear() { + assert.equal(subject.day, 1); + assert.equal(subject.month, 1); + assert.equal(subject.year, 2013); + } + + test('.month / .day beyond the year', function() { + subject.day++; + subject.month++; + + assert.equal(subject.day, 1); + assert.equal(subject.month, 2); + assert.equal(subject.year, 2013); + }); + + test('.hour', function() { + subject.hour = 23; + subject.hour++; + + movedToNextYear(); + assert.equal(subject.hour, 0); + }); + + test('.minute', function() { + subject.minute = 59; + subject.hour = 23; + subject.minute++; + + movedToNextYear(); + assert.equal(subject.hour, 0); + assert.equal(subject.minute, 0); + }); + + test('.second', function() { + subject.hour = 23; + subject.minute = 59; + subject.second = 59; + + subject.second++; + + movedToNextYear(); + assert.equal(subject.minute, 0); + assert.equal(subject.second, 0); + }); + + }); + + suite('#subtractDate and #subtractDateTz', function() { + testSupport.useTimezones('America/Los_Angeles', 'America/New_York'); + + test('diff between two times in different timezones', function() { + // 3 hours ahead of west + let east = new ICAL.Time({ + year: 2012, + month: 1, + day: 1, + hour: 10, + minute: 20, + timezone: 'America/New_York' + }); + + + let west = new ICAL.Time({ + year: 2012, + month: 1, + day: 1, + hour: 12, + minute: 50, + timezone: 'America/Los_Angeles' + }); + + let diff1 = west.subtractDate(east); + assert.hasProperties(diff1, { + hours: 2, + minutes: 30, + isNegative: false + }); + let diff2 = west.subtractDateTz(east); + assert.hasProperties(diff2, { + hours: 5, + minutes: 30, + isNegative: false + }); + }); + + test('diff between two times in same timezone', function() { + let t1 = new ICAL.Time({ + year: 2012, + month: 1, + day: 1, + hour: 21, + minute: 50, + timezone: 'America/Los_Angeles' + }); + let t2 = new ICAL.Time({ + year: 2012, + month: 1, + day: 1, + hour: 8, + minute: 30, + timezone: 'America/Los_Angeles' + }); + + let diff1 = t1.subtractDate(t2); + assert.hasProperties(diff1, { + hours: 13, + minutes: 20, + isNegative: false + }); + + let diff2 = t1.subtractDateTz(t2); + assert.hasProperties(diff2, { + hours: 13, + minutes: 20, + isNegative: false + }); + }); + test('negative absolute difference', function() { + let t1 = new ICAL.Time({ + year: 2012, + month: 1, + day: 1, + hour: 8, + minute: 30, + timezone: 'America/Los_Angeles' + }); + let t2 = new ICAL.Time({ + year: 2012, + month: 1, + day: 1, + hour: 21, + minute: 50, + timezone: 'America/Los_Angeles' + }); + + let diff = t1.subtractDate(t2); + + assert.hasProperties(diff, { + hours: 13, + minutes: 20, + isNegative: true + }); + }); + }); + + suite('#fromJSDate', function() { + + test('utc', function() { + let date = new Date(2012, 0, 1); + let expected = { + year: date.getUTCFullYear(), + // + 1 ICAL.js is not zero based... + month: date.getUTCMonth() + 1, + day: date.getUTCDate(), + hour: date.getUTCHours(), + minute: date.getUTCMinutes(), + second: date.getUTCSeconds() + }; + + let subject = Time.fromJSDate(date, true); + + assert.hasProperties( + subject, expected + ); + }); + + test('floating', function() { + let date = new Date(2012, 0, 1); + let subject = Time.fromJSDate(date); + + assert.deepEqual( + subject.toJSDate(), + date + ); + }); + + test('reset', function() { + let subject = Time.fromJSDate(null); + let expected = { + year: 1970, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + isDate: false, + timezone: "Z" + }; + + assert.hasProperties( + subject, expected + ); + }); + }); + + suite('#fromData', function() { + test('empty object', function() { + let subject = Time.fromData(); + let expected = { + year: 0, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0 + }; + + assert.hasProperties(subject, expected, 'starts at begining of time'); + }); + + test('with year, month', function() { + let subject = Time.fromData({ + year: 2012, + month: 1 + }); + + assert.hasProperties(subject, { + year: 2012, + month: 1 + }); + }); + + test('utc timezone', function() { + let subject = Time.fromData({ + year: 2012, + timezone: 'Z' + }); + + assert.hasProperties(subject, { + year: 2012, + zone: Timezone.utcTimezone + }); + }); + + test('floating timezone', function() { + let subject = Time.fromData({ + year: 2012, + timezone: 'floating' + }); + + assert.hasProperties(subject, { + year: 2012, + zone: Timezone.localTimezone + }); + }); + + test('setting icaltype', function() { + let subject = Time.fromData({ + icaltype: 'date-time', + year: 2012, + month: 1 + }); + + assert.hasProperties(subject, { + icaltype: 'date', + year: 2012, + month: 1 + }); + }); + }); + + suite('#dayOfWeek', function() { + + // format for dayOfWeek assertion + // is [dayNumber, dateObject] + let assertions = [ + [Time.SUNDAY, new Date(2012, 0, 1)], + [Time.MONDAY, new Date(2012, 0, 2)], + [Time.TUESDAY, new Date(2012, 0, 3)], + [Time.WEDNESDAY, new Date(2012, 0, 4)], + [Time.THURSDAY, new Date(2012, 0, 5)], + [Time.FRIDAY, new Date(2012, 0, 6)], + [Time.SATURDAY, new Date(2012, 0, 7)] + //TODO: Add more I was lazy here this is + // mostly to check that the function is + // sane but if there is a bug somewhere + // we can add tests above... + ]; + + assertions.forEach(function(item) { + let dayOfWeek = item[0]; + let date = item[1]; + let human = date.getFullYear() + '-' + date.getMonth() + '-' + date.getDate(); + let msg = human + ' should be #' + dayOfWeek + ' day'; + + test(msg, function() { + let subject = ICAL.Time.fromJSDate( + date + ); + + assert.equal( + subject.dayOfWeek(), + dayOfWeek + ); + }); + }); + + let assertionsWithWkst = [ + //wkst, expectedDayofWeek, date + [Time.SUNDAY, 1, new Date(2012, 0, 1)], + [Time.SUNDAY, 2, new Date(2012, 0, 2)], + [Time.SUNDAY, 3, new Date(2012, 0, 3)], + [Time.SUNDAY, 4, new Date(2012, 0, 4)], + [Time.SUNDAY, 5, new Date(2012, 0, 5)], + [Time.SUNDAY, 6, new Date(2012, 0, 6)], + [Time.SUNDAY, 7, new Date(2012, 0, 7)], + [Time.MONDAY, 7, new Date(2012, 0, 1)], + [Time.MONDAY, 1, new Date(2012, 0, 2)], + [Time.MONDAY, 2, new Date(2012, 0, 3)], + [Time.MONDAY, 3, new Date(2012, 0, 4)], + [Time.MONDAY, 4, new Date(2012, 0, 5)], + [Time.MONDAY, 5, new Date(2012, 0, 6)], + [Time.MONDAY, 6, new Date(2012, 0, 7)], + [Time.TUESDAY, 6, new Date(2012, 0, 1)], + [Time.TUESDAY, 7, new Date(2012, 0, 2)], + [Time.TUESDAY, 1, new Date(2012, 0, 3)], + [Time.TUESDAY, 2, new Date(2012, 0, 4)], + [Time.TUESDAY, 3, new Date(2012, 0, 5)], + [Time.TUESDAY, 4, new Date(2012, 0, 6)], + [Time.TUESDAY, 5, new Date(2012, 0, 7)], + [Time.WEDNESDAY, 5, new Date(2012, 0, 1)], + [Time.WEDNESDAY, 6, new Date(2012, 0, 2)], + [Time.WEDNESDAY, 7, new Date(2012, 0, 3)], + [Time.WEDNESDAY, 1, new Date(2012, 0, 4)], + [Time.WEDNESDAY, 2, new Date(2012, 0, 5)], + [Time.WEDNESDAY, 3, new Date(2012, 0, 6)], + [Time.WEDNESDAY, 4, new Date(2012, 0, 7)], + [Time.THURSDAY, 4, new Date(2012, 0, 1)], + [Time.THURSDAY, 5, new Date(2012, 0, 2)], + [Time.THURSDAY, 6, new Date(2012, 0, 3)], + [Time.THURSDAY, 7, new Date(2012, 0, 4)], + [Time.THURSDAY, 1, new Date(2012, 0, 5)], + [Time.THURSDAY, 2, new Date(2012, 0, 6)], + [Time.THURSDAY, 3, new Date(2012, 0, 7)], + [Time.FRIDAY, 3, new Date(2012, 0, 1)], + [Time.FRIDAY, 4, new Date(2012, 0, 2)], + [Time.FRIDAY, 5, new Date(2012, 0, 3)], + [Time.FRIDAY, 6, new Date(2012, 0, 4)], + [Time.FRIDAY, 7, new Date(2012, 0, 5)], + [Time.FRIDAY, 1, new Date(2012, 0, 6)], + [Time.FRIDAY, 2, new Date(2012, 0, 7)], + [Time.SATURDAY, 2, new Date(2012, 0, 1)], + [Time.SATURDAY, 3, new Date(2012, 0, 2)], + [Time.SATURDAY, 4, new Date(2012, 0, 3)], + [Time.SATURDAY, 5, new Date(2012, 0, 4)], + [Time.SATURDAY, 6, new Date(2012, 0, 5)], + [Time.SATURDAY, 7, new Date(2012, 0, 6)], + [Time.SATURDAY, 1, new Date(2012, 0, 7)] + ]; + + assertionsWithWkst.forEach(function(item) { + let wkst = item[0]; + let dayOfWeek = item[1]; + let date = item[2]; + let human = date.getFullYear() + '-' + date.getMonth() + '-' + date.getDate(); + let msg = human + ' should be #' + dayOfWeek + ' day'; + + test(msg, function() { + let subject = ICAL.Time.fromJSDate( + date + ); + + assert.equal( + subject.dayOfWeek(wkst), + dayOfWeek + ); + }); + }); + }); + + suite('#dayOfYear', function() { + let inc; + + function testYear(start) { + let end = new Date( + start.getFullYear() + 1, + start.getMonth(), + start.getDate() + ); + + let max = 400; + let cur = start; + inc = 1; + let time = Time.fromJSDate(cur); + + end = new Date( + end.getFullYear(), + end.getMonth(), + 0 + ); + + while (end.valueOf() >= cur.valueOf()) { + if (inc > max) { + throw new Error('test error inf loop'); + } + + assert.equal( + time.dayOfYear(), + inc, + cur.toString() + ); + + cur = new Date( + start.getFullYear(), + 0, + start.getDate() + inc + ); + time = Time.fromJSDate(cur); + inc++; + } + } + + test('full year (2011/no leap)', function() { + testYear(new Date(2011, 0, 1)); + assert.equal(inc - 1, 365, 'is not leap'); + }); + + test('full year (2012 + leap)', function() { + testYear(new Date(2012, 0, 1)); + assert.equal(inc - 1, 366, 'is leap'); + }); + }); + + suite('#startOfWeek', function() { + let start = new Date(2012, 1, 1); + let subject; + let expected; + + suiteSetup(function() { + let time = Time.fromJSDate(new Date( + 2012, 0, 29 + )); + + expected = { + year: time.year, + month: time.month, + day: time.day, + minute: time.minute, + second: time.second + }; + + }); + + [0, 1, 2, 3].forEach((day) => { + let date = new Date( + start.getFullYear(), + start.getMonth(), + start.getDate() + day + ); + + let msg = 'convert: "' + date.toString() + '" to first day of week'; + + test(msg, function() { + subject = Time.fromJSDate(date); + assert.hasProperties( + subject.startOfWeek(), + expected + ); + }); + }); + + }); + + suite('#getDominicalLetter', function() { + test('instance', function() { + let subject = function(yr) { + return (new ICAL.Time({ year: yr })).getDominicalLetter(); + }; + assert.equal(subject(1989), "A"); + assert.equal(subject(1990), "G"); + assert.equal(subject(1991), "F"); + assert.equal(subject(1993), "C"); + assert.equal(subject(1994), "B"); + assert.equal(subject(1997), "E"); + assert.equal(subject(1998), "D"); + + assert.equal(subject(2000), "BA"); + assert.equal(subject(2004), "DC"); + assert.equal(subject(2008), "FE"); + assert.equal(subject(2012), "AG"); + assert.equal(subject(2016), "CB"); + assert.equal(subject(2020), "ED"); + assert.equal(subject(2024), "GF"); + + }); + test('static', function() { + let subject = ICAL.Time.getDominicalLetter; + assert.equal(subject(1989), "A"); + assert.equal(subject(1990), "G"); + assert.equal(subject(1991), "F"); + assert.equal(subject(1993), "C"); + assert.equal(subject(1994), "B"); + assert.equal(subject(1997), "E"); + assert.equal(subject(1998), "D"); + + assert.equal(subject(2000), "BA"); + assert.equal(subject(2004), "DC"); + assert.equal(subject(2008), "FE"); + assert.equal(subject(2012), "AG"); + assert.equal(subject(2016), "CB"); + assert.equal(subject(2020), "ED"); + assert.equal(subject(2024), "GF"); + }); + }); + + suite('#nthWeekDay', function() { + suite('negative', function() { + test('last saturday in Sept 2012 (before current day)', function() { + let time = Time.fromData({ year: 2012, month: 9, day: 1 }); + + let day = time.nthWeekDay(Time.SATURDAY, -1); + let date = new Date(2012, 8, day); + + assert.deepEqual( + date, + new Date(2012, 8, 29) + ); + }); + + test('last Monday in Jan 2012 (target after current day)', function() { + let time = Time.fromData({ year: 2012, month: 1, day: 1 }); + + let day = time.nthWeekDay(Time.MONDAY, -1); + let date = new Date(2012, 0, day); + + assert.deepEqual( + new Date(2012, 0, 30), + date + ); + }); + + test('2nd to last friday after May 15th 2012 (multiple weeks)', function() { + let time = Time.fromData({ year: 2012, month: 5, day: 15 }); + + let day = time.nthWeekDay(Time.FRIDAY, -2); + let date = new Date(2012, 4, day); + + assert.deepEqual( + date, + new Date(2012, 4, 18) + ); + }); + + test('third to last Tuesday in April 2012 (tuesday)', function() { + let time = Time.fromData({ year: 2012, month: 4, day: 5 }); + + let day = time.nthWeekDay(Time.TUESDAY, -3); + let date = new Date(2012, 3, day); + + assert.deepEqual( + date, + new Date(2012, 3, 10) + ); + }); + + }); + + suite('positive', function() { + + test('1st wed in Feb 2012 (start is day)', function() { + let time = Time.fromData({ year: 2012, month: 2, day: 1 }); + let day = time.nthWeekDay(Time.WEDNESDAY, 0); + + let date = new Date(2012, 1, day); + assert.deepEqual( + date, + new Date(2012, 1, 1) + ); + }); + + test('1st monday in Feb 2012 (start is after day)', function() { + let time = Time.fromData({ year: 2012, month: 2, day: 1 }); + let day = time.nthWeekDay(Time.MONDAY, 0); + + let date = new Date(2012, 1, day); + + assert.deepEqual( + date, + new Date(2012, 1, 6) + ); + }); + + test('20th monday of year (multiple months)', function() { + let time = Time.fromData({ year: 2012, month: 1, day: 1 }); + + let day = time.nthWeekDay(Time.MONDAY, 20); + let date = new Date(2012, 0, day); + + assert.deepEqual( + date, + new Date(2012, 4, 14) + ); + }); + + test('3rd monday (multiple)', function() { + let time = Time.fromData({ year: 2012, month: 1, day: 1 }); + + let day = time.nthWeekDay(Time.MONDAY, 3); + let date = new Date(2012, 0, day); + + assert.deepEqual( + date, + new Date(2012, 0, 16) + ); + }); + }); + }); + + suite('#isNthWeekDay', function() { + + test('each day of the week', function() { + // Remember 1 === SUNDAY not MONDAY + let start = new Date(2012, 3, 8); + let time; + + for (let dow = 1; dow <= 7; dow++) { + time = Time.fromJSDate(new Date( + start.getFullYear(), + start.getMonth(), + 7 + dow //8, 9, etc.. + )); + + assert.isTrue( + time.isNthWeekDay(dow, 2, 31), + time.toJSDate().toString() + + ' should be 2nd occurrence of ' + dow + ' weekday' + ); + } + }); + + test('on any weekday', function() { + let dt = Time.fromString('2013-01-08'); + assert.isTrue(dt.isNthWeekDay(Time.TUESDAY, 0)); + }); + test('not weekday at all', function() { + let dt = Time.fromString('2013-01-08'); + assert.isFalse(dt.isNthWeekDay(Time.WEDNESDAY, 0)); + }); + test('not nth weekday', function() { + let dt = Time.fromString('2013-01-08'); + assert.isFalse(dt.isNthWeekDay(Time.TUESDAY, 3)); + }); + + }); + + suite('#toUnixTime', function() { + test('without timezone', function() { + let date = new Date(2012, 0, 22, 1, 7, 39); + let time = new ICAL.Time({ + year: date.getUTCFullYear(), + month: date.getUTCMonth() + 1, + day: date.getUTCDate(), + hour: date.getUTCHours(), + minute: date.getUTCMinutes(), + second: date.getUTCSeconds() + }); + + assert.equal( + time.toUnixTime(), + date.valueOf() / 1000 + ); + }); + + suite('with timezone', function() { + let icsData; + suiteSetup(async function() { + icsData = await testSupport.loadSample('timezones/America/Los_Angeles.ics'); + }); + + let subject; + let zone; + + setup(function() { + let parsed = ICAL.parse(icsData); + let vcalendar = new ICAL.Component(parsed); + let comp = vcalendar.getFirstSubcomponent('vtimezone'); + + zone = new ICAL.Timezone({ + tzid: comp.getFirstPropertyValue('tzid'), + component: comp + }); + + subject = new ICAL.Time({ + year: 2012, + month: 1, + day: 1, + hour: 10 + }, zone); + }); + + test('result', function() { + // we know that subject is -8 + let expectedTime = Date.UTC( + 2012, + 0, + 1, + 18 + ) / 1000; + + assert.equal( + subject.toUnixTime(), + expectedTime + ); + }); + }); + }); + + test('#fromUnixTime', function() { + let time = new ICAL.Time({ + year: 2012, + month: 1, + day: 5, + hour: 8, + minute: 4, + second: 13, + timezone: 'Z' + }); + + let otherTime = new ICAL.Time(); + otherTime.fromUnixTime(time.toUnixTime()); + + assert.deepEqual( + time.toJSDate(), + otherTime.toJSDate() + ); + + otherTime.fromUnixTime(time.toUnixTime() + 0.123); + + assert.equal(time.toUnixTime(), otherTime.toUnixTime()); + assert.deepEqual( + time.toJSDate(), + otherTime.toJSDate() + ); + assert.deepEqual( + time.second, + otherTime.second + ); + + let date = new ICAL.Time({ + year: 2012, + month: 1, + day: 5 + }); + + date.fromUnixTime(date.toUnixTime()); + assert.equal(date.hour, 0); + assert.equal(date.minute, 0); + assert.equal(date.second, 0); + }); + + suite('#adjust', function() { + let date = new Date(2012, 0, 25); + + test('overflow days - negative', function() { + let time = Time.fromJSDate(date); + time.adjust(-35, 0, 0, 0); + + assert.deepEqual( + time.toJSDate(), + new Date(2011, 11, 21) + ); + }); + + test('overflow days - positive', function() { + let time = Time.fromJSDate(date); + + time.adjust(20, 0, 0, 0); + + assert.deepEqual( + time.toJSDate(), + new Date(2012, 1, 14) + ); + }); + + test('overflow years normalization - negative', function() { + let time = Time.fromJSDate(date); + + time.month = 0; + time.adjust(0, 0, 0, 0); + + assert.deepEqual( + time.toJSDate(), + new Date(2011, 11, 25) + ); + }); + + test('overflow years normalization - positive', function() { + let time = Time.fromJSDate(date); + + time.month = 13; + time.adjust(0, 0, 0, 0); + + assert.deepEqual( + time.toJSDate(), + new Date(2013, 0, 25) + ); + }); + + }); + + suite('#startDoyWeek', function() { + + test('forward (using defaults)', function() { + let subject = Time.fromData({ year: 2012, month: 1, day: 20 }); + let result = subject.startDoyWeek(); + assert.equal(result, 15, 'should start on sunday of that week'); + }); + test('with different wkst', function() { + let subject = Time.fromData({ year: 2012, month: 1, day: 1 }); + let result = subject.startDoyWeek(ICAL.Time.MONDAY); + assert.equal(result, -5); + }); + test('falls on zero', function() { + let subject = Time.fromData({ year: 2013, month: 1, day: 1 }); + let result = subject.startDoyWeek(ICAL.Time.MONDAY); + assert.equal(result, 0); + }); + }); + + suite('#toString', function() { + test('from fractional seconds', function() { + let subject = new ICAL.Time({ + year: 2012, + month: 10, + day: 10, + minute: 50, + // I found this while testing in gaia + second: 8.3, + isDate: false + }); + + assert.equal( + subject.toString(), + '2012-10-10T00:50:08' + ); + }); + }); + + suite('#toICALString', function() { + test('date', function() { + let subject = ICAL.Time.fromString('2012-10-12'); + assert.equal(subject.toICALString(), '20121012'); + }); + + test('date-time', function() { + let subject = ICAL.Time.fromString('2012-10-12T07:08:09'); + assert.equal(subject.toICALString(), '20121012T070809'); + }); + }); + + suite('#toJSON', function() { + test('with utc time', function() { + let time = new Time({ + year: 2012, + day: 1, + month: 1, + hour: 3, + zone: Timezone.utcTimezone + }); + + let after = new Time(time.toJSON()); + assert.equal(after.zone, Timezone.utcTimezone); + + assert.deepEqual( + after.toJSDate(), + time.toJSDate() + ); + }); + + test('with floating time', function() { + let time = new Time({ + year: 2012, + month: 1, + day: 1, + hour: 2, + minute: 15, + second: 1, + isDate: false, + zone: Timezone.localTimezone + }); + + let expected = { + year: 2012, + month: 1, + day: 1, + hour: 2, + minute: 15, + second: 1, + isDate: false, + timezone: 'floating' + }; + + assert.deepEqual(time.toJSON(), expected); + + let after = new Time(time.toJSON()); + assert.equal(after.zone, Timezone.localTimezone); + + assert.deepEqual( + time.toJSDate(), + after.toJSDate() + ); + }); + + test('with null timezone', function() { + let time = new Time({ + year: 2012, + month: 1, + day: 1, + hour: 2, + minute: 15, + second: 1, + isDate: false, + }); + time.zone = null; + + let expected = { + year: 2012, + month: 1, + day: 1, + hour: 2, + minute: 15, + second: 1, + isDate: false, + }; + + assert.deepEqual(time.toJSON(), expected); + }); + }); + + test('calculations', function() { + + let test_data = [{ + str: '2012-01-01T00:00:00', + expect_unixtime: 1325376000, + expect_1s: '2012-01-01T00:00:01', + expect_1m: '2012-01-01T00:01:00', + expect_1h: '2012-01-01T01:00:00', + expect_1d: '2012-01-02T00:00:00', + expect_1w: '2012-01-08T00:00:00' + }]; + + for (let datakey in test_data) { + let data = test_data[datakey]; + let dt = Time.fromString(data.str); + let cp = dt.clone(); + + assert.equal(dt.toUnixTime(), data.expect_unixtime); + let dur = dt.subtractDate(Time.epochTime); + assert.equal(dur.toSeconds(), data.expect_unixtime); + + cp = dt.clone(); + cp.year += 1; + + let diff = cp.subtractDate(dt); + let yearseconds = (365 + Time.isLeapYear(dt.year)) * 86400; + assert.equal(diff.toSeconds(), yearseconds); + + cp = dt.clone(); + cp.year += 2; + diff = cp.subtractDate(dt); + yearseconds = (365 + Time.isLeapYear(dt.year) + 365 + Time.isLeapYear(dt.year + 1)) * 86400; + assert.equal(diff.toSeconds(), yearseconds); + + cp = dt.clone(); + cp.year -= 1; + diff = cp.subtractDate(dt); + yearseconds = (365 + Time.isLeapYear(cp.year)) * 86400; + assert.equal(diff.toSeconds(), -yearseconds); + + cp = dt.clone(); + cp.second += 3; + diff = cp.subtractDate(dt); + assert.equal(diff.toSeconds(), 3); + + cp = dt.clone(); + cp.addDuration(ICAL.Duration.fromString('PT1S')); + assert.equal(cp, data.expect_1s); + cp.addDuration(ICAL.Duration.fromString('-PT1S')); + assert.equal(cp.toString(), dt.toString()); + + cp.addDuration(ICAL.Duration.fromString('PT1M')); + assert.equal(cp, data.expect_1m); + cp.addDuration(ICAL.Duration.fromString('-PT1M')); + assert.equal(cp.toString(), dt.toString()); + + cp.addDuration(ICAL.Duration.fromString('PT1H')); + assert.equal(cp, data.expect_1h); + cp.addDuration(ICAL.Duration.fromString('-PT1H')); + assert.equal(cp.toString(), dt.toString()); + + cp.addDuration(ICAL.Duration.fromString('P1D')); + assert.equal(cp, data.expect_1d); + cp.addDuration(ICAL.Duration.fromString('-P1D')); + assert.equal(cp.toString(), dt.toString()); + + cp.addDuration(ICAL.Duration.fromString('P1W')); + assert.equal(cp, data.expect_1w); + cp.addDuration(ICAL.Duration.fromString('-P1W')); + assert.equal(cp.toString(), dt.toString()); + + + cp = dt.clone(); + cp.addDuration(ICAL.Duration.fromString('PT24H')); + cp.isDate = true; + + // force normalize + // eslint-disable-next-line no-unused-expressions + cp.isDate; + + cp.isDate = false; + assert.equal(cp, data.expect_1d); + } + }); + + test('#normalize', function() { + let test_data = [{ + str: '2012-12-31T23:59:59', + add_seconds: 1, + expect: '2013-01-01T00:00:00' + }, { + str: '2011-01-01T00:00:00', + add_seconds: -1, + expect: '2010-12-31T23:59:59' + }]; + + for (let datakey in test_data) { + let data = test_data[datakey]; + let dt = Time.fromString(data.str); + let add_seconds = data.add_seconds || 0; + + dt.second += add_seconds; + assert.equal(dt, data.expect); + } + }); + + suite('date properites', function() { + function testDateProperties(str, data, only) { + (only ? test.only : test)(str, function() { + let dt = Time.fromString(str); + assert.equal(data.isDate, dt.isDate); + assert.equal(data.year, dt.year); + assert.equal(data.month, dt.month); + assert.equal(data.day, dt.day); + assert.equal(data.hour, dt.hour); + assert.equal(data.minute, dt.minute); + assert.equal(data.second, dt.second); + assert.equal(data.leap_year, Time.isLeapYear(dt.year)); + assert.equal(data.dayOfWeek, dt.dayOfWeek().toString()); + assert.equal(data.dayOfYear, dt.dayOfYear().toString()); + assert.equal(data.startOfWeek, dt.startOfWeek().toString()); + assert.equal(data.endOfWeek, dt.endOfWeek().toString()); + assert.equal(data.startOfMonth, dt.startOfMonth().toString()); + assert.equal(data.endOfMonth, dt.endOfMonth().toString()); + assert.equal(data.startOfYear, dt.startOfYear().toString()); + assert.equal(data.endOfYear, dt.endOfYear().toString()); + assert.equal(data.startDoyWeek, dt.startDoyWeek(Time.SUNDAY)); + assert.equal(data.weekNumber, dt.weekNumber(Time.SUNDAY)); + assert.equal(data.getDominicalLetter, dt.getDominicalLetter()); + // TODO nthWeekDay + + dt = new Time(); + dt.resetTo(data.year, data.month, data.day, data.hour, data.minute, + data.second, Timezone.utcTimezone); + assert.equal(data.year, dt.year); + assert.equal(data.month, dt.month); + assert.equal(data.day, dt.day); + assert.equal(data.hour, dt.hour); + assert.equal(data.minute, dt.minute); + assert.equal(data.second, dt.second); + }); + } + testDateProperties.only = function(str, data) { + testDateProperties(str, data, true); + }; + + // A date where the year starts on sunday + testDateProperties('2012-01-01T00:00:00', { + isDate: false, + year: 2012, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + leap_year: true, + dayOfWeek: Time.SUNDAY, + dayOfYear: 1, + startOfWeek: '2012-01-01', + endOfWeek: '2012-01-07', + startOfMonth: '2012-01-01', + endOfMonth: '2012-01-31', + startOfYear: '2012-01-01', + endOfYear: '2012-12-31', + startDoyWeek: 1, + weekNumber: 1, + getDominicalLetter: 'AG' + }); + // A date in week number 53 + testDateProperties('2005-01-01T00:00:00', { + isDate: false, + year: 2005, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + leap_year: false, + dayOfWeek: Time.SATURDAY, + dayOfYear: 1, + startOfWeek: '2004-12-26', + endOfWeek: '2005-01-01', + startOfMonth: '2005-01-01', + endOfMonth: '2005-01-31', + startOfYear: '2005-01-01', + endOfYear: '2005-12-31', + getDominicalLetter: 'B', + startDoyWeek: -5, + weekNumber: 53 + }); + // A time in week number 28 + testDateProperties('2015-07-08T01:02:03', { + isDate: false, + year: 2015, + month: 7, + day: 8, + hour: 1, + minute: 2, + second: 3, + leap_year: false, + dayOfWeek: Time.WEDNESDAY, + dayOfYear: 189, + startOfWeek: '2015-07-05', + endOfWeek: '2015-07-11', + startOfMonth: '2015-07-01', + endOfMonth: '2015-07-31', + startOfYear: '2015-01-01', + endOfYear: '2015-12-31', + startDoyWeek: 186, + getDominicalLetter: 'D', + weekNumber: 28 + }); + }); + + test('startOfWeek with different first day of week', function() { + let test_data = [{ /* A Sunday */ + str: '2012-01-01T12:01:00', + firstDayOfWeek: { + SUNDAY: '2012-01-01', + MONDAY: '2011-12-26', + TUESDAY: '2011-12-27', + WEDNESDAY: '2011-12-28', + THURSDAY: '2011-12-29', + FRIDAY: '2011-12-30', + SATURDAY: '2011-12-31' + } + }, { /* A Monday */ + str: '2012-01-02T12:01:00', + firstDayOfWeek: { + SUNDAY: '2012-01-01', + MONDAY: '2012-01-02', + TUESDAY: '2011-12-27', + WEDNESDAY: '2011-12-28', + THURSDAY: '2011-12-29', + FRIDAY: '2011-12-30', + SATURDAY: '2011-12-31' + } + }, { /* A Tuesday */ + str: '2012-01-03T12:01:00', + firstDayOfWeek: { + SUNDAY: '2012-01-01', + MONDAY: '2012-01-02', + TUESDAY: '2012-01-03', + WEDNESDAY: '2011-12-28', + THURSDAY: '2011-12-29', + FRIDAY: '2011-12-30', + SATURDAY: '2011-12-31' + } + }, { /* A Wednesday */ + str: '2012-01-04T12:01:00', + firstDayOfWeek: { + SUNDAY: '2012-01-01', + MONDAY: '2012-01-02', + TUESDAY: '2012-01-03', + WEDNESDAY: '2012-01-04', + THURSDAY: '2011-12-29', + FRIDAY: '2011-12-30', + SATURDAY: '2011-12-31' + } + }, { /* A Thursday */ + str: '2012-01-05T12:01:00', + firstDayOfWeek: { + SUNDAY: '2012-01-01', + MONDAY: '2012-01-02', + TUESDAY: '2012-01-03', + WEDNESDAY: '2012-01-04', + THURSDAY: '2012-01-05', + FRIDAY: '2011-12-30', + SATURDAY: '2011-12-31' + } + }, { /* A Friday */ + str: '2012-01-06T12:01:00', + firstDayOfWeek: { + SUNDAY: '2012-01-01', + MONDAY: '2012-01-02', + TUESDAY: '2012-01-03', + WEDNESDAY: '2012-01-04', + THURSDAY: '2012-01-05', + FRIDAY: '2012-01-06', + SATURDAY: '2011-12-31' + } + }, { /* A Saturday */ + str: '2012-01-07T12:01:00', + firstDayOfWeek: { + SUNDAY: '2012-01-01', + MONDAY: '2012-01-02', + TUESDAY: '2012-01-03', + WEDNESDAY: '2012-01-04', + THURSDAY: '2012-01-05', + FRIDAY: '2012-01-06', + SATURDAY: '2012-01-07' + } + }]; + + for (let datakey in test_data) { + let data = test_data[datakey]; + let dt = Time.fromString(data.str); + for (let day in data.firstDayOfWeek) { + assert.equal(data.firstDayOfWeek[day], dt.startOfWeek(ICAL.Time[day]).toString()); + } + } + }); + + test('endOfWeek with different first day of week', function() { + let test_data = [{ /* A Sunday */ + str: '2012-01-01T12:01:00', + firstDayOfWeek: { + SUNDAY: '2012-01-07', + MONDAY: '2012-01-01', + TUESDAY: '2012-01-02', + WEDNESDAY: '2012-01-03', + THURSDAY: '2012-01-04', + FRIDAY: '2012-01-05', + SATURDAY: '2012-01-06' + } + }, { /* A Monday */ + str: '2012-01-02T12:01:00', + firstDayOfWeek: { + SUNDAY: '2012-01-07', + MONDAY: '2012-01-08', + TUESDAY: '2012-01-02', + WEDNESDAY: '2012-01-03', + THURSDAY: '2012-01-04', + FRIDAY: '2012-01-05', + SATURDAY: '2012-01-06' + } + }, { /* A Tuesday */ + str: '2012-01-03T12:01:00', + firstDayOfWeek: { + SUNDAY: '2012-01-07', + MONDAY: '2012-01-08', + TUESDAY: '2012-01-09', + WEDNESDAY: '2012-01-03', + THURSDAY: '2012-01-04', + FRIDAY: '2012-01-05', + SATURDAY: '2012-01-06' + } + }, { /* A Wednesday */ + str: '2012-01-04T12:01:00', + firstDayOfWeek: { + SUNDAY: '2012-01-07', + MONDAY: '2012-01-08', + TUESDAY: '2012-01-09', + WEDNESDAY: '2012-01-10', + THURSDAY: '2012-01-04', + FRIDAY: '2012-01-05', + SATURDAY: '2012-01-06' + } + }, { /* A Thursday */ + str: '2012-01-05T12:01:00', + firstDayOfWeek: { + SUNDAY: '2012-01-07', + MONDAY: '2012-01-08', + TUESDAY: '2012-01-09', + WEDNESDAY: '2012-01-10', + THURSDAY: '2012-01-11', + FRIDAY: '2012-01-05', + SATURDAY: '2012-01-06' + } + }, { /* A Friday */ + str: '2012-01-06T12:01:00', + firstDayOfWeek: { + SUNDAY: '2012-01-07', + MONDAY: '2012-01-08', + TUESDAY: '2012-01-09', + WEDNESDAY: '2012-01-10', + THURSDAY: '2012-01-11', + FRIDAY: '2012-01-12', + SATURDAY: '2012-01-06' + } + }, { /* A Saturday */ + str: '2012-01-07T12:01:00', + firstDayOfWeek: { + SUNDAY: '2012-01-07', + MONDAY: '2012-01-08', + TUESDAY: '2012-01-09', + WEDNESDAY: '2012-01-10', + THURSDAY: '2012-01-11', + FRIDAY: '2012-01-12', + SATURDAY: '2012-01-13' + } + }]; + + for (let datakey in test_data) { + let data = test_data[datakey]; + let dt = Time.fromString(data.str); + for (let day in data.firstDayOfWeek) { + assert.equal(data.firstDayOfWeek[day], dt.endOfWeek(ICAL.Time[day]).toString()); + } + } + }); + + suite('#compare', function() { + testSupport.useTimezones('America/New_York', 'America/Los_Angeles'); + + test('simple comparison', function() { + let a = Time.fromString('2001-01-01T00:00:00'); + let b = Time.fromString('2001-01-01T00:00:00'); + assert.equal(a.compare(b), 0); + + b = Time.fromString('2002-01-01T00:00:00'); + assert.equal(a.compare(b), -1); + assert.equal(b.compare(a), 1); + + b = Time.fromString('2001-02-01T00:00:00'); + assert.equal(a.compare(b), -1); + assert.equal(b.compare(a), 1); + + b = Time.fromString('2001-01-02T00:00:00'); + assert.equal(a.compare(b), -1); + assert.equal(b.compare(a), 1); + + b = Time.fromString('2001-01-01T01:00:00'); + assert.equal(a.compare(b), -1); + assert.equal(b.compare(a), 1); + + b = Time.fromString('2001-01-01T00:01:00'); + assert.equal(a.compare(b), -1); + assert.equal(b.compare(a), 1); + + b = Time.fromString('2001-01-01T00:00:01'); + assert.equal(a.compare(b), -1); + assert.equal(b.compare(a), 1); + }); + + test('simple comparison one with a timezone, one without', function() { + // Floating timezone is effectively UTC. New York is 5 hours behind. + let a = Time.fromString('2001-01-01T00:00:00'); + a.zone = ICAL.TimezoneService.get('America/New_York'); + let b = Time.fromString('2001-01-01T05:00:00'); + b.zone = Timezone.localTimezone; + assert.equal(a.compare(b), 0); + + b = Time.fromString('2002-01-01T05:00:00'); + assert.equal(a.compare(b), -1); + assert.equal(b.compare(a), 1); + + b = Time.fromString('2001-02-01T05:00:00'); + assert.equal(a.compare(b), -1); + assert.equal(b.compare(a), 1); + + b = Time.fromString('2001-01-02T05:00:00'); + assert.equal(a.compare(b), -1); + assert.equal(b.compare(a), 1); + + b = Time.fromString('2001-01-01T06:00:00'); + assert.equal(a.compare(b), -1); + assert.equal(b.compare(a), 1); + + b = Time.fromString('2001-01-01T05:01:00'); + assert.equal(a.compare(b), -1); + assert.equal(b.compare(a), 1); + + b = Time.fromString('2001-01-01T05:00:01'); + assert.equal(a.compare(b), -1); + assert.equal(b.compare(a), 1); + }); + + test('date-only comparison', function() { + let a = Time.fromString('2001-01-01'); + let b = Time.fromString('2001-01-01'); + assert.equal(a.compareDateOnlyTz(b, Timezone.localTimezone), 0); + + b = Time.fromString('2002-01-01'); + assert.equal(a.compareDateOnlyTz(b, Timezone.localTimezone), -1); + assert.equal(b.compareDateOnlyTz(a, Timezone.localTimezone), 1); + + b = Time.fromString('2001-02-01'); + assert.equal(a.compareDateOnlyTz(b, Timezone.localTimezone), -1); + assert.equal(b.compareDateOnlyTz(a, Timezone.localTimezone), 1); + + b = Time.fromString('2001-01-02'); + assert.equal(a.compareDateOnlyTz(b, Timezone.localTimezone), -1); + assert.equal(b.compareDateOnlyTz(a, Timezone.localTimezone), 1); + }); + + test('both are dates', function() { + let a = Time.fromString('2014-07-20'); + a.zone = ICAL.TimezoneService.get('America/New_York'); + let b = Time.fromString('2014-07-20'); + b.zone = Timezone.localTimezone; + + assert.ok(a.isDate); + assert.ok(b.isDate); + + assert.equal(a.compareDateOnlyTz(b, a.zone), 0); + assert.equal(a.compareDateOnlyTz(b, b.zone), 0); + assert.equal(b.compareDateOnlyTz(a, a.zone), 0); + assert.equal(b.compareDateOnlyTz(a, b.zone), 0); + + // Midnight in New York is after midnight UTC. + assert.equal(a.compare(b), 1); + assert.equal(b.compare(a), -1); + }); + + test('one is date, one isnt', function() { + let a = Time.fromString('2014-07-20T12:00:00.000'); + a.zone = ICAL.TimezoneService.get('America/New_York'); + let b = Time.fromString('2014-07-20'); + b.zone = Timezone.localTimezone; + + assert.ok(!a.isDate); + assert.ok(b.isDate); + + assert.equal(a.compareDateOnlyTz(b, a.zone), 0); + assert.equal(a.compareDateOnlyTz(b, b.zone), 0); + assert.equal(b.compareDateOnlyTz(a, a.zone), 0); + assert.equal(b.compareDateOnlyTz(a, b.zone), 0); + + // Midday in New York is after midnight UTC. + assert.equal(a.compare(b), 1); + assert.equal(b.compare(a), -1); + }); + + test('one is date, one isnt', function() { + let a = Time.fromString('2014-07-20T12:00:00.000'); + a.zone = Timezone.localTimezone; + let b = Time.fromString('2014-07-20'); + b.zone = ICAL.TimezoneService.get('America/New_York'); + + assert.ok(!a.isDate); + assert.ok(b.isDate); + + assert.equal(a.compareDateOnlyTz(b, a.zone), 0); + assert.equal(a.compareDateOnlyTz(b, b.zone), 0); + assert.equal(b.compareDateOnlyTz(a, a.zone), 0); + assert.equal(b.compareDateOnlyTz(a, b.zone), 0); + + // Midday UTC is after midnight in New York. + assert.equal(a.compare(b), 1); + assert.equal(b.compare(a), -1); + }); + + test('both are not dates', function() { + let a = Time.fromString('2014-07-20T12:00:00.000'); + a.zone = ICAL.TimezoneService.get('America/New_York'); + let b = Time.fromString('2014-07-20T12:00:00.000'); + b.zone = Timezone.localTimezone; + + assert.ok(!a.isDate); + assert.ok(!b.isDate); + + assert.equal(a.compareDateOnlyTz(b, a.zone), 0); + assert.equal(a.compareDateOnlyTz(b, b.zone), 0); + assert.equal(b.compareDateOnlyTz(a, a.zone), 0); + assert.equal(b.compareDateOnlyTz(a, b.zone), 0); + + // Midday in New York is after midday UTC. + assert.equal(a.compare(b), 1); + assert.equal(b.compare(a), -1); + }); + + test('two timezones', function() { + let a = Time.fromString('2014-07-20T02:00:00.000'); + a.zone = ICAL.TimezoneService.get('America/New_York'); + let b = Time.fromString('2014-07-19T23:00:00.000'); + b.zone = ICAL.TimezoneService.get('America/Los_Angeles'); + + assert.ok(!a.isDate); + assert.ok(!b.isDate); + + assert.equal(a.compareDateOnlyTz(b, a.zone), 0); + assert.equal(a.compareDateOnlyTz(b, b.zone), 0); + assert.equal(b.compareDateOnlyTz(a, a.zone), 0); + assert.equal(b.compareDateOnlyTz(a, b.zone), 0); + assert.equal(a.compare(b), 0); + assert.equal(b.compare(a), 0); + + a.isDate = true; + b.isDate = true; + + assert.equal(a.compareDateOnlyTz(b, a.zone), 1); + assert.equal(a.compareDateOnlyTz(b, b.zone), 1); + assert.equal(b.compareDateOnlyTz(a, a.zone), -1); + assert.equal(b.compareDateOnlyTz(a, b.zone), -1); + assert.equal(a.compare(b), 1); + assert.equal(b.compare(a), -1); + }); + }); + + test('cache cleared', function() { + // This test ensures the cached Unix time is cleared whenever the time is changed. + let time = new Time({ + year: 2015, + month: 4, + day: 3, + hour: 12, + minute: 34, + second: 56, + zone: Timezone.utcTimezone + }); + + assert.equal(time.toUnixTime(), 1428064496); + time.year++; + assert.equal(time.toUnixTime(), 1459686896); + time.month++; + assert.equal(time.toUnixTime(), 1462278896); + time.day++; + assert.equal(time.toUnixTime(), 1462365296); + time.hour++; + assert.equal(time.toUnixTime(), 1462368896); + time.minute++; + assert.equal(time.toUnixTime(), 1462368956); + time.second++; + assert.equal(time.toUnixTime(), 1462368957); + + time.adjust(-397, -1, -1, -1); + assert.equal(time.toUnixTime(), 1428064496); + + time.resetTo(2016, 5, 4, 13, 35, 57); + assert.equal(time.toUnixTime(), 1462368957); + + // time.fromString('2015-04-03T12:34:56Z'); + // assert.equal(time.toUnixTime(), 1428064496); + + time.fromJSDate(new Date(Date.UTC(2015, 0, 1)), true); + assert.equal(time.toUnixTime(), 1420070400); + + time.fromData({ + year: 2015, + month: 4, + day: 3, + hour: 12, + minute: 34, + second: 56, + zone: Timezone.utcTimezone + }); + assert.equal(time.toUnixTime(), 1428064496); + + time.addDuration(ICAL.Duration.fromString('P1D')); + assert.equal(time.toUnixTime(), 1428150896); + + time.fromUnixTime(1234567890); + assert.equal(time.toUnixTime(), 1234567890); + }); + + suite("static functions", function() { + test('daysInMonth', function() { + assert.equal(Time.daysInMonth(0, 2011), 30); + assert.equal(Time.daysInMonth(2, 2012), 29); + assert.equal(Time.daysInMonth(2, 2013), 28); + assert.equal(Time.daysInMonth(13, 2014), 30); + }); + + test('isLeapYear', function() { + assert.isTrue(Time.isLeapYear(1752)); + assert.isTrue(Time.isLeapYear(2000)); + assert.isTrue(Time.isLeapYear(2004)); + assert.isFalse(Time.isLeapYear(2100)); + }); + + test('fromDayOfYear', function() { + assert.equal(Time.fromDayOfYear(-730, 2001).toICALString(), "19990101"); + assert.equal(Time.fromDayOfYear(-366, 2001).toICALString(), "19991231"); + assert.equal(Time.fromDayOfYear(-365, 2001).toICALString(), "20000101"); + assert.equal(Time.fromDayOfYear(0, 2001).toICALString(), "20001231"); + assert.equal(Time.fromDayOfYear(365, 2001).toICALString(), "20011231"); + assert.equal(Time.fromDayOfYear(366, 2001).toICALString(), "20020101"); + assert.equal(Time.fromDayOfYear(730, 2001).toICALString(), "20021231"); + assert.equal(Time.fromDayOfYear(731, 2001).toICALString(), "20030101"); + assert.equal(Time.fromDayOfYear(1095, 2001).toICALString(), "20031231"); + assert.equal(Time.fromDayOfYear(1096, 2001).toICALString(), "20040101"); + assert.equal(Time.fromDayOfYear(1461, 2001).toICALString(), "20041231"); + assert.equal(Time.fromDayOfYear(1826, 2001).toICALString(), "20051231"); + }); + + test('fromStringv2', function() { + let subject = Time.fromStringv2("2015-01-01"); + let expected = { + year: 2015, + month: 1, + day: 1, + hour: 0, + minute: 0, + second: 0, + isDate: true, + timezone: "floating" + }; + + assert.deepEqual( + subject.toJSON(), expected + ); + }); + + suite("weekOneStarts", function() { + function testWeekOne(year, dates, only) { + let dom = ICAL.Time.getDominicalLetter(year); + (only ? test.only : test)(year + " (" + dom + ")", function() { + for (let wkday in dates) { + let icalwkday = ICAL.Time[wkday]; + let w1st = Time.weekOneStarts(year, icalwkday); + assert.equal(dates[wkday], w1st.toString(), wkday); + + let startOfWeek = ICAL.Time.fromString(dates[wkday]); + assert.equal(startOfWeek.weekNumber(icalwkday), 1, wkday); + startOfWeek.day--; + assert.isAbove(startOfWeek.weekNumber(icalwkday), 51, wkday); + } + }); + } + testWeekOne.only = function(year, dates) { + testWeekOne(year, dates, true); + }; + + test('default week start', function() { + let w1st = Time.weekOneStarts(1989); + assert.equal('1989-01-02', w1st.toString()); + }); + + testWeekOne(1989, { // A and AG + SUNDAY: '1989-01-01', + MONDAY: '1989-01-02', + TUESDAY: '1989-01-03', + WEDNESDAY: '1989-01-04', + THURSDAY: '1989-01-05', + FRIDAY: '1988-12-30', + SATURDAY: '1988-12-31' + }); + testWeekOne(1994, { // B and BA + SUNDAY: '1994-01-02', + MONDAY: '1994-01-03', + TUESDAY: '1994-01-04', + WEDNESDAY: '1994-01-05', + THURSDAY: '1994-01-06', + FRIDAY: '1993-12-31', + SATURDAY: '1994-01-01' + }); + testWeekOne(1993, { // C and CB + SUNDAY: '1993-01-03', + MONDAY: '1993-01-04', + TUESDAY: '1993-01-05', + WEDNESDAY: '1993-01-06', + THURSDAY: '1993-01-07', + FRIDAY: '1993-01-01', + SATURDAY: '1993-01-02' + }); + testWeekOne(1998, { // D and DC + SUNDAY: '1997-12-28', + MONDAY: '1997-12-29', + TUESDAY: '1997-12-30', + WEDNESDAY: '1997-12-31', + THURSDAY: '1998-01-01', + FRIDAY: '1997-12-26', + SATURDAY: '1997-12-27' + }); + testWeekOne(1997, { // E and ED + SUNDAY: '1996-12-29', + MONDAY: '1996-12-30', + TUESDAY: '1996-12-31', + WEDNESDAY: '1997-01-01', + THURSDAY: '1997-01-02', + FRIDAY: '1996-12-27', + SATURDAY: '1996-12-28' + }); + testWeekOne(1991, { // F and FE + SUNDAY: '1990-12-30', + MONDAY: '1990-12-31', + TUESDAY: '1991-01-01', + WEDNESDAY: '1991-01-02', + THURSDAY: '1991-01-03', + FRIDAY: '1990-12-28', + SATURDAY: '1990-12-29' + }); + testWeekOne(1990, { // G and GF + SUNDAY: '1989-12-31', + MONDAY: '1990-01-01', + TUESDAY: '1990-01-02', + WEDNESDAY: '1990-01-03', + THURSDAY: '1990-01-04', + FRIDAY: '1989-12-29', + SATURDAY: '1989-12-30' + }); + }); + }); +}); diff --git a/test/timezone_service_test.js b/test/timezone_service_test.js new file mode 100644 index 0000000..e2285f9 --- /dev/null +++ b/test/timezone_service_test.js @@ -0,0 +1,116 @@ +suite('timezone_service', function() { + let icsData; + suiteSetup(async function() { + icsData = await testSupport.loadSample('timezones/America/Los_Angeles.ics'); + }); + + let subject; + setup(function() { + subject = ICAL.TimezoneService; + subject.reset(); + }); + + teardown(function() { + subject.reset(); + }); + + test('init', function() { + // This tests the default behavior when the time zone service is first initialized + subject._hard_reset(); + assert.isFalse(subject.has('UTC')); + + subject._hard_reset(); + assert.equal(subject.count, 0); + assert.isFalse(subject.has('UTC')); + + subject._hard_reset(); + assert.isNull(subject.remove('bogus')); + assert.isFalse(subject.has('UTC')); + + // Getting a timezone will initialize the service and set UTC + subject._hard_reset(); + assert.isUndefined(subject.get('bogus')); + assert.isTrue(subject.has('UTC')); + }); + + test('utc zones', function() { + let zones = ['Z', 'UTC', 'GMT']; + zones.forEach(function(tzid) { + assert.ok(subject.has(tzid), tzid + ' should exist'); + assert.equal(subject.get(tzid), ICAL.Timezone.utcTimezone); + }); + }); + + test('#reset', function() { + let name = 'ZFOO'; + subject.register(name, ICAL.Timezone.utcTimezone); + assert.isTrue(subject.has(name), 'should have set ' + name); + + subject.reset(); + assert.isFalse(subject.has(name), 'removes ' + name + ' after reset'); + + assert.equal(subject.count, 3); + }); + + suite('register zones', function() { + test('when it does not exist', function() { + let name = 'test'; + assert.isFalse(subject.has(name)); + + assert.equal(subject.count, 3); + subject.register(name, ICAL.Timezone.localTimezone); + assert.equal(subject.count, 4); + assert.isTrue(subject.has(name), 'is present after set'); + assert.equal( + subject.get(name), + ICAL.Timezone.localTimezone + ); + + subject.remove(name); + assert.isFalse(subject.has(name), 'can remove zones'); + }); + + test('with invalid type', function() { + assert.throws(function() { + subject.register('zzz', 'fff'); + }, "timezone must be ICAL.Timezone"); + }); + test('with only invalid component', function() { + assert.throws(function() { + let comp = new ICAL.Component('vtoaster'); + subject.register(comp); + }, "Neither a timezone nor a name was passed"); + }); + + test('override', function() { + // don't do this but you can if you want to shoot + // yourself in the foot. + assert.equal(subject.count, 3); + subject.register('Z', ICAL.Timezone.localTimezone); + + assert.equal( + subject.get('Z'), + ICAL.Timezone.localTimezone + ); + assert.equal(subject.count, 3); + }); + + test('using a component', function() { + let parsed = ICAL.parse(icsData); + let comp = new ICAL.Component(parsed); + let vtimezone = comp.getFirstSubcomponent('vtimezone'); + let tzid = vtimezone.getFirstPropertyValue('tzid'); + + assert.equal(subject.count, 3); + subject.register(vtimezone); + assert.equal(subject.count, 4); + + assert.isTrue(subject.has(tzid), 'successfully registed with component'); + + let zone = subject.get(tzid); + + assert.instanceOf(zone, ICAL.Timezone); + assert.equal(zone.tzid, tzid); + }); + }); +}); diff --git a/test/timezone_test.js b/test/timezone_test.js new file mode 100644 index 0000000..2d80dfa --- /dev/null +++ b/test/timezone_test.js @@ -0,0 +1,323 @@ +suite('timezone', function() { + let timezone; + + function timezoneTest(tzid, name, testCb) { + if (typeof(name) === 'function') { + testCb = name; + name = 'parse'; + } + + suite(tzid, function() { + if (tzid == "UTC") { + setup(function() { + timezone = ICAL.Timezone.utcTimezone; + }); + } else if (tzid == "floating") { + setup(function() { + timezone = ICAL.Timezone.localTimezone; + }); + } else { + setup(async function() { + let icsData = await testSupport.loadSample('timezones/' + tzid + '.ics'); + + let parsed = ICAL.parse(icsData); + let vcalendar = new ICAL.Component(parsed); + let comp = vcalendar.getFirstSubcomponent('vtimezone'); + + timezone = new ICAL.Timezone(comp); + }); + } + + test(name, testCb); + }); + } + + function utcHours(time) { + let seconds = timezone.utcOffset( + new ICAL.Time(time) + ); + + // in hours + return (seconds / 3600); + } + + function sanityCheckSuite(options) { + let runner = options.only ? suite.only : suite; + let title = 'time: ' + JSON.stringify(options.time); + + runner(title, function() { + for (let tzid in options.offsets) { + timezoneTest(tzid, tzid + " offset " + options.offsets[tzid], function(testTzid) { + assert.equal( + utcHours(options.time), + options.offsets[testTzid] + ); + }.bind(this, tzid)); + } + }); + } + sanityCheckSuite.only = function(options) { + options.only = true; + sanityCheckSuite(options); + }; + + + // just before US DST + sanityCheckSuite({ + time: { year: 2012, month: 3, day: 11, hour: 1, minute: 59 }, + offsets: { + 'America/Los_Angeles': -8, + 'America/New_York': -5, + 'America/Denver': -7, + 'America/Atikokan': -5, // single tz + 'UTC': 0, + 'floating': 0 + } + }); + + // just after US DST + sanityCheckSuite({ + time: { year: 2012, month: 3, day: 11, hour: 2 }, + offsets: { + 'America/Los_Angeles': -7, + 'America/Denver': -6, + 'America/New_York': -4, + 'America/Atikokan': -5, + 'UTC': 0, + 'floating': 0 + } + }); + + sanityCheckSuite({ + time: { year: 2004, month: 10, day: 31, hour: 0, minute: 59, second: 59 }, + offsets: { + 'America/Denver': -6 + } + }); + + sanityCheckSuite({ + time: { year: 2004, month: 10, day: 31, hour: 1 }, + offsets: { + 'America/Denver': -7 + } + }); + + + // Edge case timezone that defines an RDATE with VALUE=DATE + sanityCheckSuite({ + // just before DST + time: { year: 1980, month: 1, day: 1, hour: 0, minute: 59 }, + offsets: { + 'Makebelieve/RDATE_test': -4, + 'Makebelieve/RDATE_utc_test': -5 + } + }); + + sanityCheckSuite({ + // just after DST + time: { year: 1980, month: 1, day: 1, hour: 1 }, + offsets: { + 'Makebelieve/RDATE_test': -5, + 'Makebelieve/RDATE_utc_test': -5 + } + }); + + // Edge case where RDATE is defined in UTC + sanityCheckSuite({ + // just before DST + time: { year: 1990, month: 1, day: 1, hour: 0, minute: 59 }, + offsets: { + 'Makebelieve/RDATE_test': -4, + 'Makebelieve/RDATE_utc_test': -4 + } + }); + + sanityCheckSuite({ + // just after DST + time: { year: 1990, month: 1, day: 1, hour: 2 }, + offsets: { + 'Makebelieve/RDATE_test': -5, + 'Makebelieve/RDATE_utc_test': -5 + } + }); + + // Edge case timezone where an RRULE with UNTIL in UTC is specified + sanityCheckSuite({ + // Just before DST + time: { year: 1975, month: 1, day: 1, hour: 1, minute: 0, second: 0 }, + offsets: { + 'Makebelieve/RRULE_UNTIL_test': -5 + } + }); + + sanityCheckSuite({ + // Just after DST + time: { year: 1975, month: 1, day: 1, hour: 3, minute: 0, second: 0 }, + offsets: { + 'Makebelieve/RRULE_UNTIL_test': -4 + } + }); + + sanityCheckSuite({ + // After the RRULE ends + time: { year: 1985, month: 1, day: 1, hour: 3, minute: 0, second: 0 }, + offsets: { + 'Makebelieve/RRULE_UNTIL_test': -4 + } + }); + + timezoneTest('America/Los_Angeles', '#expandedUntilYear', function() { + + function calcYear(yr) { + return Math.max(ICAL.Timezone._minimumExpansionYear, yr) + + ICAL.Timezone.EXTRA_COVERAGE; + } + + let time = new ICAL.Time({ + year: 2032, + zone: timezone + }); + let expectedCoverage = calcYear(time.year); + + time.utcOffset(); + assert.equal(timezone.expandedUntilYear, expectedCoverage); + + time = new ICAL.Time({ + year: 2034, + zone: timezone + }); + + time.utcOffset(); + assert.equal(timezone.expandedUntilYear, expectedCoverage); + + time = new ICAL.Time({ + year: 1997, + zone: timezone + }); + time.utcOffset(); + assert.equal(timezone.expandedUntilYear, expectedCoverage); + + time = new ICAL.Time({ + year: expectedCoverage + 3, + zone: timezone + }); + expectedCoverage = calcYear(time.year); + time.utcOffset(); + assert.equal(timezone.expandedUntilYear, expectedCoverage); + }); + + suite('#convertTime', function() { + timezoneTest('America/Los_Angeles', 'convert date-time from utc', function() { + let subject = ICAL.Time.fromString('2012-03-11T01:59:00Z'); + let subject2 = subject.convertToZone(timezone); + assert.equal(subject2.zone.tzid, timezone.tzid); + assert.equal(subject2.toString(), '2012-03-10T17:59:00'); + }); + + timezoneTest('America/Los_Angeles', 'convert date from utc', function() { + let subject = ICAL.Time.fromString('2012-03-11'); + let subject2 = subject.convertToZone(timezone); + assert.equal(subject2.zone.tzid, timezone.tzid); + assert.equal(subject2.toString(), '2012-03-11'); + }); + timezoneTest('America/Los_Angeles', 'convert local time to zone', function() { + let subject = ICAL.Time.fromString('2012-03-11T01:59:00'); + subject.zone = ICAL.Timezone.localTimezone; + assert.equal(subject.toString(), '2012-03-11T01:59:00'); + + let subject2 = subject.convertToZone(timezone); + assert.equal(subject2.toString(), '2012-03-11T01:59:00'); + + let subject3 = subject2.convertToZone(ICAL.Timezone.localTimezone); + assert.equal(subject3.toString(), '2012-03-11T01:59:00'); + }); + }); + + suite('#fromData', function() { + timezoneTest('America/Los_Angeles', 'string component', function() { + let subject = new ICAL.Timezone({ + component: timezone.component.toString(), + tzid: 'Makebelieve/Different' + }); + + assert.equal(subject.expandedUntilYear, 0); + assert.equal(subject.tzid, 'Makebelieve/Different'); + assert.equal(subject.component.getFirstPropertyValue('tzid'), 'America/Los_Angeles'); + }); + + timezoneTest('America/Los_Angeles', 'component in data', function() { + let subject = new ICAL.Timezone({ + component: timezone.component, + }); + + assert.equal(subject.tzid, 'America/Los_Angeles'); + assert.deepEqual(subject.component, timezone.component); + }); + + timezoneTest('America/Los_Angeles', 'with strange component', function() { + let subject = new ICAL.Timezone({ + component: 123 + }); + + assert.isNull(subject.component); + }); + }); + + suite('#utcOffset', function() { + test('empty vtimezone', function() { + let subject = new ICAL.Timezone({ + component: new ICAL.Component('vtimezone') + }); + + assert.equal(subject.utcOffset(ICAL.Time.fromString('2012-01-01')), 0); + }); + + test('empty STANDARD/DAYLIGHT', function() { + let subject = new ICAL.Timezone({ + component: new ICAL.Component(['vtimezone', [], [ + ['standard', [], []], + ['daylight', [], []] + ]]) + }); + + assert.equal(subject.utcOffset(ICAL.Time.fromString('2012-01-01')), 0); + }); + }); + + suite('#toString', function() { + timezoneTest('America/Los_Angeles', 'toString', function() { + assert.equal(timezone.toString(), "America/Los_Angeles"); + assert.equal(timezone.tzid, "America/Los_Angeles"); + assert.equal(timezone.tznames, ""); + + timezone.tznames = "test"; + assert.equal(timezone.toString(), "test"); + assert.equal(timezone.tzid, "America/Los_Angeles"); + assert.equal(timezone.tznames, "test"); + }); + }); + + test('#_compare_change_fn', function() { + let subject = ICAL.Timezone._compare_change_fn; + + let a = new ICAL.Time({ + year: 2015, + month: 6, + day: 15, + hour: 12, + minute: 30, + second: 30 + }); + + function vary(prop) { + let b = a.clone(); + assert.equal(subject(a, b), 0); + b[prop] += 1; + assert.equal(subject(a, b), -1); + b[prop] -= 2; + assert.equal(subject(a, b), 1); + } + + ['year', 'month', 'day', 'hour', 'minute', 'second'].forEach(vary); + }); +}); diff --git a/test/utc_offset_test.js b/test/utc_offset_test.js new file mode 100644 index 0000000..c8b603c --- /dev/null +++ b/test/utc_offset_test.js @@ -0,0 +1,116 @@ +suite('ICAL.UtcOffset', function() { + test('#clone', function() { + let subject = new ICAL.UtcOffset({ hours: 5, minutes: 6 }); + assert.equal(subject.toString(), "+05:06"); + + let cloned = subject.clone(); + subject.hours = 6; + + assert.equal(cloned.toString(), "+05:06"); + assert.equal(subject.toString(), "+06:06"); + }); + + test('#toICALString', function() { + let subject = new ICAL.UtcOffset({ hours: 5, minutes: 6 }); + assert.equal(subject.toString(), "+05:06"); + assert.equal(subject.toICALString(), "+0506"); + }); + + suite('#normalize', function() { + test('minute overflow', function() { + assert.hasProperties(new ICAL.UtcOffset({ + minutes: 120 + }), { + hours: 2, minutes: 0, factor: 1 + }); + }); + test('minutes underflow', function() { + assert.hasProperties(new ICAL.UtcOffset({ + minutes: -120 + }), { + hours: 2, minutes: 0, factor: -1 + }); + }); + test('minutes underflow with hours', function() { + assert.hasProperties(new ICAL.UtcOffset({ + hours: 2, + minutes: -120 + }), { + hours: 0, minutes: 0, factor: 1 + }); + }); + test('hours overflow', function() { + assert.hasProperties(new ICAL.UtcOffset({ + hours: 15, + minutes: 30 + }), { + hours: 11, minutes: 30, factor: -1 + }); + }); + test('hours underflow', function() { + assert.hasProperties(new ICAL.UtcOffset({ + hours: 13, + minutes: 30, + factor: -1 + }), { + hours: 13, minutes: 30, factor: 1 + }); + }); + test('hours double underflow', function() { + assert.hasProperties(new ICAL.UtcOffset({ + hours: 40, + minutes: 30, + factor: -1 + }), { + hours: 13, minutes: 30, factor: 1 + }); + }); + test('negative zero utc offset', function() { + assert.hasProperties(new ICAL.UtcOffset({ + hours: 0, + minutes: 0, + factor: -1 + }), { + hours: 0, minutes: 0, factor: -1 + }); + + }); + }); + + suite('#compare', function() { + test('greater', function() { + let a = new ICAL.UtcOffset({ hours: 5, minutes: 1 }); + let b = new ICAL.UtcOffset({ hours: 5, minutes: 0 }); + assert.equal(a.compare(b), 1); + }); + test('equal', function() { + let a = new ICAL.UtcOffset({ hours: 15, minutes: 0 }); + let b = new ICAL.UtcOffset({ hours: -12, minutes: 0 }); + assert.equal(a.compare(b), 0); + }); + test('equal zero', function() { + let a = new ICAL.UtcOffset({ hours: 0, minutes: 0, factor: -1 }); + let b = new ICAL.UtcOffset({ hours: 0, minutes: 0 }); + assert.equal(a.compare(b), 0); + }); + test('less than', function() { + let a = new ICAL.UtcOffset({ hours: 5, minutes: 0 }); + let b = new ICAL.UtcOffset({ hours: 5, minutes: 1 }); + assert.equal(a.compare(b), -1); + }); + }); + + suite('from/toSeconds', function() { + test('static', function() { + let subject = ICAL.UtcOffset.fromSeconds(3661); + assert.equal(subject.toString(), '+01:01'); + assert.equal(subject.toSeconds(), 3660); + }); + test('instance', function() { + let subject = ICAL.UtcOffset.fromSeconds(3661); + subject.fromSeconds(-7321); + assert.equal(subject.toString(), '-02:02'); + assert.equal(subject.toSeconds(), -7320); + }); + }); +}); diff --git a/test/vcard_time_test.js b/test/vcard_time_test.js new file mode 100644 index 0000000..11b02d5 --- /dev/null +++ b/test/vcard_time_test.js @@ -0,0 +1,69 @@ +suite('vcard time', function() { + // Lots of things are also covered in the design test + + suite('initialization', function() { + test('default icaltype', function() { + let subject = ICAL.VCardTime.fromDateAndOrTimeString('2015-01-01'); + assert.equal(subject.icaltype, 'date-and-or-time'); + }); + + test('clone', function() { + let orig = ICAL.VCardTime.fromDateAndOrTimeString('2015-01-02T03:04:05-08:00', 'date-time'); + let subject = orig.clone(); + + orig.day++; + orig.month++; + orig.year++; + orig.hour++; + orig.minute++; + orig.second++; + orig.zone = ICAL.Timezone.utcTimezone; + + assert.equal(orig.toString(), '2016-02-03T04:05:06Z'); + assert.equal(subject.toString(), '2015-01-02T03:04:05-08:00'); + assert.equal(subject.icaltype, 'date-time'); + assert.equal(subject.zone.toString(), '-08:00'); + }); + }); + + suite('#utcOffset', function() { + testSupport.useTimezones('America/New_York'); + + test('floating and utc', function() { + let subject = ICAL.VCardTime.fromDateAndOrTimeString('2015-01-02T03:04:05', 'date-time'); + subject.zone = ICAL.Timezone.utcTimezone; + assert.equal(subject.utcOffset(), 0); + + subject.zone = ICAL.Timezone.localTimezone; + assert.equal(subject.utcOffset(), 0); + }); + test('ICAL.UtcOffset', function() { + let subject = ICAL.VCardTime.fromDateAndOrTimeString('2015-01-02T03:04:05-08:00', 'date-time'); + assert.equal(subject.utcOffset(), -28800); + }); + test('Olson timezone', function() { + let subject = ICAL.VCardTime.fromDateAndOrTimeString('2015-01-02T03:04:05'); + subject.zone = ICAL.TimezoneService.get('America/New_York'); + assert.equal(subject.utcOffset(), -18000); + }); + }); + + suite('#toString', function() { + testSupport.useTimezones('America/New_York'); + + test('invalid icaltype', function() { + let subject = ICAL.VCardTime.fromDateAndOrTimeString('2015-01-01', 'ballparkfigure'); + assert.isNull(subject.toString()); + }); + test('invalid timezone', function() { + let subject = ICAL.VCardTime.fromDateAndOrTimeString('2015-01-01T01:01:01'); + subject.zone = null; + assert.equal(subject.toString(), '2015-01-01T01:01:01'); + }); + test('Olson timezone', function() { + let subject = ICAL.VCardTime.fromDateAndOrTimeString('2015-01-02T03:04:05'); + subject.zone = ICAL.TimezoneService.get('America/New_York'); + assert.equal(subject.toString(), '2015-01-02T03:04:05-05:00'); + }); + }); +}); diff --git a/tools/ICALTester/Makefile b/tools/ICALTester/Makefile new file mode 100644 index 0000000..ed45a2e --- /dev/null +++ b/tools/ICALTester/Makefile @@ -0,0 +1,32 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# Portions Copyright (C) Philipp Kewisch, 2015 + +LIBICAL_BASE ?= $(CURDIR)/../libical +LIBICAL_BUILD = $(LIBICAL_BASE)/build +LDFLAGS = $(LIBICAL_BUILD)/lib/libical.a +CFLAGS = -I $(LIBICAL_BUILD)/src -g + +LIBICAL_DEFINES = -DSTATIC_ONLY=True -DENABLE_GTK_DOC=OFF + +LIBICAL_SOURCES = support/libical-recur.c +LIBICAL_PROG = support/libical-recur + +all: libical + +libical: $(LIBICAL_BUILD)/lib/libical.a + $(CC) -o $(LIBICAL_PROG) $(LIBICAL_SOURCES) $(LDFLAGS) $(CFLAGS) + +clean: + $(RM) -r $(LIBICAL_BASE) + +run: + node index.js + +$(LIBICAL_BASE): + cd $(dir $(LIBICAL_BASE)) && git clone https://github.com/libical/libical + +$(LIBICAL_BUILD)/lib/libical.a: $(LIBICAL_BASE) + mkdir -p $(LIBICAL_BUILD) + cd $(LIBICAL_BUILD) && cmake $(LIBICAL_DEFINES) .. && make diff --git a/tools/ICALTester/README.md b/tools/ICALTester/README.md new file mode 100644 index 0000000..536ffd8 --- /dev/null +++ b/tools/ICALTester/README.md @@ -0,0 +1,59 @@ +ICALTester +========== + +This is a simple program to compare various ICAL recurrence implementations +with ICAL.js. It generates random rules based on the format defined in +`rules.json` and runs them through the various libraries. + +Running +------- + +The usage goes as follows: + +```bash +$ node compare.js rules.json ./support/libical-recur +``` + +* The first argument to `compare.js` is the path to the `rules.json` (described + further down). An example file is provided. +* The second argument is the path to a binary executed for comparison. + +The binary should be able to take arguments as in the following example; +it expects the same output: + +```bash +# Usage: ./support/libical-recur +$ ./support/libical-recur "FREQ=MONTHLY;BYDAY=1FR,3SU" "2014-11-11T08:00:00" 10 +20141116T080000 +20141205T080000 +20141221T080000 +20150102T080000 +20150118T080000 +20150206T080000 +20150215T080000 +20150306T080000 +20150315T080000 +20150403T080000 +``` + +The **`libical-recur`** binary can be built using the provided `Makefile`. + +rules.json +---------- + +The format is the same as what can be passed to `ICAL.Recur.fromData()`, with one +addition: If the value is `%`, the tester generates a random rule value. + +Example: + +```json +[ + { "freq": "MONTHLY", "bymonthday": "%" } +] +``` + +Possible Result: + +``` +RRULE:FREQ=MONTHLY;BYMONTHDAY=1,15,17,20,31 +``` diff --git a/tools/ICALTester/compare.js b/tools/ICALTester/compare.js new file mode 100644 index 0000000..6f0dd90 --- /dev/null +++ b/tools/ICALTester/compare.js @@ -0,0 +1,105 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch, 2015 */ + +import fs from "fs"; +import { spawn } from "child_process"; +import difflet from "difflet"; +import Tester from "./lib/ICALTester.js"; +import ICAL from "../../lib/ical/module.js"; + +function setupHandlers(binPath) { + Tester.addHandler("icaljs", function(rule, dtstart, max, callback) { + let iter = rule.iterator(dtstart); + let occ = 0; + let start = new Date(); + + let results = []; + (function loop() { + let next, diff; + + if (++occ > max) { + return callback(results); + } + + try { + next = iter.next(); + } catch (e) { + return callback(e.message || e); + } + + if (next) { + results.push(next.toICALString()); + } else { + return callback(results); + } + + diff = (new Date() - start) / 1000; + if (diff > Tester.MAX_EXECUTION_TIME) { + return callback("Maximum execution time exceeded"); + } + + setImmediate(loop); + })(); + }); + + Tester.addHandler("other", function(rule, dtstart, max, callback) { + let results = []; + let ptimer = null; + let recur = spawn(binPath, [rule.toString(), dtstart.toICALString(), max]); + + recur.stdout.on('data', function(data) { + Array.prototype.push.apply(results, data.toString().split("\n").slice(0, -1)); + }); + + recur.on('close', function(code) { + if (ptimer) { + clearTimeout(ptimer); + } + + if (code === null) { + callback("Maximum execution time exceeded"); + } else if (code !== 0) { + callback("Execution error: " + code); + } else { + callback(null, results); + } + }); + + ptimer = setTimeout(function() { + ptimer = null; + recur.kill(); + }, Tester.MAX_EXECUTION_TIME); + }); +} + +function usage(message) { + if (message) { + console.log("Error: " + message); + } + console.log("Usage: ICALTester rules.json /path/to/binary"); + process.exit(1); +} + +function main() { + if (process.argv.length < 4) { + usage(); + } + + let rulesFile = fs.statSync(process.argv[2]) && process.argv[2]; + let binPath = fs.statSync(process.argv[3]) && process.argv[3]; + let ruleData = JSON.parse(fs.readFileSync(rulesFile)); + + let dtstart = ICAL.Time.fromString("2014-11-11T08:00:00"); + let max = 10; + + setupHandlers(binPath); + + Tester.run(ruleData, dtstart, max, function(err, results) { + let diff = difflet({ indent: true, comments: true }); + console.log(diff.compare(results.other, results.icaljs)); + }); +} + +main(); diff --git a/tools/ICALTester/lib/ICALTester.js b/tools/ICALTester/lib/ICALTester.js new file mode 100644 index 0000000..13592c4 --- /dev/null +++ b/tools/ICALTester/lib/ICALTester.js @@ -0,0 +1,173 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch, 2015 */ + + +/** + * ICALTester Module + * @module ICALTester + */ + +import ICAL from "../../../lib/ical/module.js"; +import async from "async"; + +function range(min, max, nozero) { + let res = []; + for (let i = min; i <= max; i++) { + if (i == 0 && nozero) { + continue; + } + res.push(i); + } + return res; +} + +function withempty(res, empty) { + return [empty || ""].concat(res); +} + +function randInt(min, max) { + return Math.floor(Math.random() * max) + min; +} + +function randList(list, count) { + let vals = list.slice(); + let res = []; + + while (count--) { + res.push(vals.splice(randInt(0, vals.length), 1)[0]); + } + return res; +} + +function randValues(designdata) { + let count = randInt(1, designdata.length); + return randList(designdata, count); +} + +function sortByday(aRules, aWeekStart) { + let thisobj = { + ruleDayOfWeek: ICAL.RecurIterator.prototype.ruleDayOfWeek, + sort_byday_rules: ICAL.RecurIterator.prototype.sort_byday_rules, + rule: { wkst: "MO" } + }; + + return thisobj.sort_byday_rules(aRules, aWeekStart || ICAL.Time.MONDAY); +} + +function sortNumeric(a, b) { + return a - b; +} + +function substitute(rules) { + return rules.map(function(r) { + for (let key in r) { + if (r[key] == "%") { + r[key] = generators[key](r); + } + } + return ICAL.Recur.fromData(r); + }); +} + +function addHandler(name, callback) { + asyncHandler[name] = callback; +} + +function runHandler(handler, rules, dtstart, max, callback) { + let res = {}; + async.eachLimit(rules, CONCURRENCY, function(rule, eachcb) { + handler(rule, dtstart, max, function(err, result) { + res[rule] = err || result; + eachcb(); + }); + }, function(err) { + callback(null, res); + }); +} + +function run(ruleData, dtstart, max, callback) { + let runner = (CONCURRENCY == 1 ? async.series : async.parallel); + let boundAsyncHandler = {}; + let rules = substitute(ruleData); + Object.keys(asyncHandler).forEach(function(k) { + boundAsyncHandler[k] = runHandler.bind(null, asyncHandler[k], rules, dtstart, max); + }); + runner(boundAsyncHandler, callback); +} + +let CONCURRENCY = 2; +let MAX_EXECUTION_TIME = 1000; +let asyncHandler = {}; + +let day_names = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU']; +let freq_values = ['SECONDLY', 'MINUTELY', 'HOURLY', 'DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY']; +let design = { + FREQ: freq_values, + + BYSECOND: range(0, 60), + BYMINUTE: range(0, 59), + BYHOUR: range(0, 23), + + BYDAY: { + DAILY: [withempty(range(-5, 5, true)), day_names], + WEEKLY: [withempty(range(-5, 5, true)), day_names], + MONTHLY: [withempty(range(-5, 5, true)), day_names], + YEARLY: [withempty(range(-53, 53, true)), day_names] + }, + BYMONTHDAY: range(1, 31), /// TOOO -31 + BYYEARDAY: range(-366, 366), + + BYWEEKNO: range(-53, 53), + BYMONTH: range(1, 12), + BYSETPOS: range(-366, 366), + WKST: day_names +}; + +let generators = { + byday: function(rule) { + let designdata = design.BYDAY[rule.freq]; + + let daycount = randInt(1, designdata[1].length); + let days = randList(designdata[1], daycount); + let prefix = randList(designdata[0], daycount); + + sortByday(days, rule.wkst); + + return days.map(function(day, i) { + return prefix[i] + day; + }); + }, + bymonthday: function(rule) { + return randValues(design.BYMONTHDAY).sort(sortNumeric); + }, + bymonth: function(rule) { + return randValues(design.BYMONTH).sort(sortNumeric); + } +}; + + +/** + * gjslint complains about missing docs + * @ignore + */ +export default { + /** + * The number of concurrent threads to use + * @type {Number} + */ + get CONCURRENCY() { return CONCURRENCY; }, + set CONCURRENCY(v) { CONCURRENCY = v; }, + + /** + * The maximum execution time + * @type {Number} + */ + get MAX_EXECUTION_TIME() { return MAX_EXECUTION_TIME; }, + set MAX_EXECUTION_TIME(v) { MAX_EXECUTION_TIME = v; }, + + generators: generators, + addHandler: addHandler, + run: run +}; diff --git a/tools/ICALTester/package-lock.json b/tools/ICALTester/package-lock.json new file mode 100644 index 0000000..5e1532f --- /dev/null +++ b/tools/ICALTester/package-lock.json @@ -0,0 +1,1081 @@ +{ + "name": "icaltester", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "icaltester", + "version": "1.0.0", + "license": "MPL-2.0", + "devDependencies": { + "difflet": "^1.0.1" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/charm": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", + "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==", + "dev": true + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/difflet": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/difflet/-/difflet-1.0.1.tgz", + "integrity": "sha512-M5odUun54PsYRWAVCUOTF975cuQN1MBYgi46lxZ+00X7A0wbkJTp+1zyr979lVpRS/gOMpekqhDMlBKmP2oXjg==", + "dev": true, + "dependencies": { + "charm": "0.1.x", + "deep-is": "0.1.x", + "traverse": "0.6.x" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/traverse": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.9.tgz", + "integrity": "sha512-7bBrcF+/LQzSgFmT0X5YclVqQxtv7TDJ1f8Wj7ibBu/U6BMLeOpUxuZjV7rMc44UtKxlnMFigdhFAIszSX1DMg==", + "dev": true, + "dependencies": { + "gopd": "^1.0.1", + "typedarray.prototype.slice": "^1.0.3", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typedarray.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typedarray.prototype.slice/-/typedarray.prototype.slice-1.0.3.tgz", + "integrity": "sha512-8WbVAQAUlENo1q3c3zZYuy5k9VzBQvp8AX9WOtbvyWlLM1v5JaSRmjubLjzHF4JFtptjH/5c/i95yaElvcjC0A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-errors": "^1.3.0", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-offset": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + } + } +} diff --git a/tools/ICALTester/package.json b/tools/ICALTester/package.json new file mode 100644 index 0000000..0fee27c --- /dev/null +++ b/tools/ICALTester/package.json @@ -0,0 +1,19 @@ +{ + "name": "icaltester", + "version": "1.0.0", + "private": true, + "author": "Philipp Kewisch", + "contributors": [ + "Github Contributors (https://github.com/kewisch/ical.js/graphs/contributors)" + ], + "description": "Compare ical.js recurrence against other implementations", + "license": "MPL-2.0", + "main": "compare.js", + "type": "module", + "devDependencies": { + "difflet": "^1.0.1" + }, + "scripts": { + "compare": "node compare.js rules.json support/libical-recur" + } +} diff --git a/tools/ICALTester/rules.json b/tools/ICALTester/rules.json new file mode 100644 index 0000000..d9eb2dc --- /dev/null +++ b/tools/ICALTester/rules.json @@ -0,0 +1,16 @@ +[ + { "freq": "DAILY" }, + { "freq": "DAILY", "byday": ["MO", "TU", "WE", "TH", "FR"] }, + { "freq": "WEEKLY" }, + { "freq": "WEEKLY", "interval": 2 }, + { "freq": "MONTHLY" }, + { "freq": "YEARLY" }, + + { "freq": "WEEKLY", "byday": "%" }, + + { "freq": "MONTHLY", "bymonthday": "%" }, + { "freq": "MONTHLY", "byday": "%" }, + + { "freq": "YEARLY", "bymonthday": "%", "bymonth": "%" }, + { "freq": "YEARLY", "byday": "%", "bymonth": "%" } +] diff --git a/tools/ICALTester/support/.gitignore b/tools/ICALTester/support/.gitignore new file mode 100644 index 0000000..d8b8e8d --- /dev/null +++ b/tools/ICALTester/support/.gitignore @@ -0,0 +1,2 @@ +*.dSYM +libical-recur diff --git a/tools/ICALTester/support/libical-recur.c b/tools/ICALTester/support/libical-recur.c new file mode 100644 index 0000000..fbdc0db --- /dev/null +++ b/tools/ICALTester/support/libical-recur.c @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch, 2011-2015 */ + +#include +#include +#include + +int main(int argc, char *argv[]) { + struct icalrecurrencetype recur; + icalrecur_iterator *ritr; + struct icaltimetype dtstart, next; + int howmany = 1; + + if (argc < 4) { + printf("Usage: libical-recur \n"); + exit(1); + } + + howmany = atoi(argv[3]); + dtstart = icaltime_from_string(argv[2]); + recur = icalrecurrencetype_from_string(argv[1]); + ritr = icalrecur_iterator_new(recur, dtstart); + + if (ritr) { + for (next = icalrecur_iterator_next(ritr); + howmany > 0 && !icaltime_is_null_time(next); + next = icalrecur_iterator_next(ritr), howmany--) { + printf("%s\n", icaltime_as_ical_string_r(next)); + } + } else { + printf("Error: %d\n", icalerrno); + } +} diff --git a/tools/benchmark/.gitignore b/tools/benchmark/.gitignore new file mode 100644 index 0000000..5a02663 --- /dev/null +++ b/tools/benchmark/.gitignore @@ -0,0 +1,4 @@ +# This directory holds ical.js files that can be used for performance testing +# Drop in a file called ical_foo.js or ical_bar.cjs and then npm run test-performance +* +!.gitignore diff --git a/tools/jsdoc-collect-types.cjs b/tools/jsdoc-collect-types.cjs new file mode 100644 index 0000000..6a79585 --- /dev/null +++ b/tools/jsdoc-collect-types.cjs @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch, 2024 */ + +/** + * This jsdoc plugin collects all classes and static typedefs defined in the library + * which start with `ICAL.` and writes those to a temporary file which is read + * by the jsdoc-ical plugin. + * This is needed because of jsdoc plugin limitations. + */ + +const fs = require("node:fs"); +let gIcalClasses = new Set(); +let typedefs = new Set(); + +exports.handlers = { + newDoclet: function({ doclet }) { + if (doclet.kind == "class" && doclet.longname.startsWith("ICAL.")) { + gIcalClasses.add(doclet.name); + } + if (doclet.kind === "typedef" && doclet.scope === "static" && doclet.longname.startsWith("ICAL.")) { + typedefs.add({name: doclet.name, full: doclet.longname}) + } + }, + processingComplete: function() { + fs.writeFileSync("./tools/jsdoc-symbols-temp.json", JSON.stringify({gIcalClasses: Array.from(gIcalClasses), typedefs: Array.from(typedefs)})); + } +}; diff --git a/tools/jsdoc-ical.cjs b/tools/jsdoc-ical.cjs new file mode 100644 index 0000000..7ab3289 --- /dev/null +++ b/tools/jsdoc-ical.cjs @@ -0,0 +1,78 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch, 2024 */ + +/** + * This jsdoc plugin add the correct prefix `ICAL.` to everything that + * uses a class or typedef which are collected by the jsdoc-collect-types plugin + * so that the generated API docs link to the correct classes, typedefs, etc. + */ + +const fs = require("node:fs"); +let gIcalClasses = []; +let typedefs = []; + +function addPrefix(objNames) { + if (objNames?.length) { + for (let i = 0; i < objNames.length; i++) { + // finds all strings in objNames[i] that include a known class + let classNames = gIcalClasses.reduce((acc, curr) => { + if (objNames[i] === curr) acc = [...acc, curr]; + return acc; + }, []); + if ((gIcalClasses.includes(objNames[i]) || classNames.length !== 0) && !objNames[i].includes("ICAL.")) { + for (let className of classNames) { + let index = objNames[i].indexOf(className); + // add ICAL. to classnames without ICAL. + objNames[i] = objNames[i].substring(0, index) + "ICAL." + objNames[i].substring(index); + } + } + // add correct typedef path to anything using a known typedef + let typedef = typedefs.find((val) => objNames[i].includes(val.name)); + if (typedef !== undefined) { + // replace partial name with full path + let result = objNames[i].replace(typedef.name, typedef.full); + objNames[i] = result; + } + } + } +} + +function augmentTypes(obj) { + // handle @type + if (obj.names?.length) { + addPrefix(obj.names); + } + // handle @returns or @params + if (obj.type?.names?.length) { + addPrefix(obj.type?.names); + } +} + +exports.handlers = { + parseBegin: function() { + ({ gIcalClasses, typedefs } = JSON.parse(fs.readFileSync("./tools/jsdoc-symbols-temp.json", "utf-8"))); + }, + newDoclet: function({ doclet }) { + if (doclet.type) { + augmentTypes(doclet.type); + } + + if (doclet.returns) { + for (let ret of doclet.returns) { + augmentTypes(ret); + } + } + if (doclet.params) { + for (let param of doclet.params) { + augmentTypes(param); + } + } + if (doclet.properties) { + for (let property of doclet.properties) { + augmentTypes(property); + } + } + } +}; diff --git a/tools/recur-tester.html b/tools/recur-tester.html new file mode 100644 index 0000000..2f75f5c --- /dev/null +++ b/tools/recur-tester.html @@ -0,0 +1,140 @@ + + + + + + + + +
+

Recurrence Rule Tester

+

+ This tool allows you to calculate occurrences for RRULEs and prepare testcases for them. It + will use ICAL.js from https://unpkg.com/ical.js. Be sure to manually validate the + occurrences, as otherwise it wouldn't be a good test. +

+
+ + +
+
+ + +
+
+ + + + +
+
+
+

+      
+

+    
+ + diff --git a/tools/scriptutils.js b/tools/scriptutils.js new file mode 100644 index 0000000..ed5d102 --- /dev/null +++ b/tools/scriptutils.js @@ -0,0 +1,161 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * Portions Copyright (C) Philipp Kewisch */ + +import { Octokit } from "@octokit/core"; +import fetch from 'node-fetch'; +import fs from "fs/promises"; +import fsc from "fs"; +import path from "path"; +import { pipeline } from "stream/promises"; + + +const octokit = new Octokit(); + +async function get_latest_release(outFile) { + let response = await octokit.request('GET /repos/{owner}/{repo}/releases/latest', { + owner: 'kewisch', + repo: 'ical.js' + }); + + let release = response.data.name; + + let icaljsAsset = response.data.assets.find(asset => asset.name == "ical.js"); + if (!icaljsAsset) { + console.error("ical.js asset missing from " + release); + } + response = await fetch(icaljsAsset.browser_download_url); + + let icaljs = await response.text(); + + await fs.writeFile(outFile, icaljs); + console.log("Latest release written to " + outFile); +} + + +async function get_latest_main(outFile) { + let response = await octokit.request("GET /repos/{owner}/{repo}/actions/runs", { + workflow_id: "ci.yml", + branch: "es6", + //branch: "main", + status: "success", + //exclude_pull_requests: true, + //event: "push", + owner: "kewisch", + repo: "ical.js" + }); + + let workflows = response.data.workflow_runs; + + workflows.sort((a, b) => { + let datea = new Date(a); + let dateb = new Date(b); + + return (datea < dateb) - (dateb < datea); + }); + + let archive_download_url = `https://nightly.link/kewisch/ical.js/actions/runs/${workflows[0].id}/distribution.zip`; + console.log(archive_download_url); + response = await fetch(archive_download_url); + if (!response.ok) { + throw new Error(response.status); + } + + let buffer = Buffer.from(await response.arrayBuffer()); + + // Yauzl has been difficult due to the native bindings for crc32. + // Import ad-hoc to make GitHub Actions work. + let yauzl = await import("yauzl-promise"); + + let archive = await yauzl.fromBuffer(buffer); + + let entry; + do { + entry = await archive.readEntry(); + } while (entry && entry.fileName == "ical.js"); + + if (!entry) { + throw new Error("ical.js not found in distribution"); + } + + let stream = await entry.openReadStream(); + let writeStream = fsc.createWriteStream(outFile); + + await pipeline(stream, writeStream); + + console.log("Latest main written to " + outFile); +} + +async function performance_downloader() { + await Promise.allSettled([ + get_latest_main("./tools/benchmark/ical_main.cjs"), + get_latest_release("./tools/benchmark/ical_release.js") + ]); +} + +async function generateZonesFile(tzdbDir) { + async function processZone(zoneFile) { + let contents = await fs.readFile(zoneFile, "utf-8"); + let lines = contents.split("\r\n"); + let vtimezone = lines.slice(lines.indexOf("BEGIN:VTIMEZONE") + 1, lines.indexOf("END:VTIMEZONE")).join("\r\n"); + return ` register(${JSON.stringify(vtimezone)});`; + } + + let tzdbVersion = (await fs.readFile(path.join(tzdbDir, "version"), "utf-8")).trim(); + + let lines = [ + `(function() {`, + ` function register(tzdata) { ICAL.TimezoneService.register(ICAL.Component.fromString("BEGIN:VTIMEZONE\\r\\n" + tzdata + "\\r\\nEND:VTIMEZONE")) };`, + ` ICAL.TimezoneService.IANA_TZDB_VERSION = "${tzdbVersion}";` + ]; + + let contents = await fs.readFile(path.join(tzdbDir, "zoneinfo", "zones.tab"), "utf-8"); + for (let line of contents.split("\n")) { + let parts = line.split(" "); + if (parts.length == 3 && parts[2].length) { + lines.push(await processZone(path.join(tzdbDir, "zoneinfo", parts[2] + ".ics"))); + } else if (parts.length == 1 && parts[0].length) { + lines.push(await processZone(path.join(tzdbDir, "zoneinfo", parts[0] + ".ics"))); + } + } + + lines.push("})();"); + + return lines.join("\n"); +} + +async function get_tzdb_version() { + let response = await fetch('https://www.iana.org/time-zones'); + let text = await response.text(); + + let match = text.match(/version">([0-9a-z]*)<\/span>/); + if (!match) { + throw new Error('Could not detect latest timezone database version'); + } + return match[1]; +} + +async function replace_unpkg(input, output) { + let content = await fs.readFile(input, { encoding: "utf-8" }); + let pkg = JSON.parse(await fs.readFile(path.join(import.meta.dirname, "..", "package.json"), { encoding: "utf-8" })); + await fs.writeFile(output, content.replace(/unpkg.com\/ical.js/g, `unpkg.com/ical.js@${pkg.version}/dist/ical.js`)); + console.log(`unpkg link from ${input} updated to ${pkg.version} and written to ${output}`); +} + +async function main() { + switch (process.argv[2]) { + case "tzdb-version": + console.log(await get_tzdb_version()); + break; + case "generate-zones": + console.log(await generateZonesFile(process.argv[3])); + break; + case "performance-downloader": + await performance_downloader(); + break; + case "replace-unpkg": + await replace_unpkg(process.argv[3], process.argv[4]); + } +} +main(); diff --git a/tools/validator.html b/tools/validator.html new file mode 100644 index 0000000..8845c84 --- /dev/null +++ b/tools/validator.html @@ -0,0 +1,117 @@ + + + + + + + + + +
+

iCalendar / jCal Validator

+

This validator form takes either iCalendar or jCal data. iCalendar data will be parsed to jCal and re-serialized to iCalendar. Similarly, jCal data will be parsed to iCalendar and re-serialized to jCal.

+
+
+

+
+

+      
+      
+    
+ + diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..62ab0c2 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,6 @@ +{ + // dont compile types directly with tsc, use npm run build instead + // type generation is handled by @rollup/plugin-typescript + + // this file exists because of https://github.com/rollup/plugins/issues/1572 +}