From 4f04ac708b769f8cdf9f1f43cb1a1cf9adeb1b04 Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Mon, 30 Mar 2026 15:20:17 -0400 Subject: [PATCH 001/190] feat: apply next branch changes onto main Replays the net delta of all unique work from the 'next' branch onto main's history, fixing the history divergence caused by git-filter-repo rewriting all commit hashes on next. Includes: ADM integration, data quality dashboard, editable tables, stix-list refactors, theme updates, and CI fixes. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/ci.yml | 62 +- .gitignore | 1 + .releaserc | 4 - Dockerfile | 3 + angular.json | 16 +- karma.conf.js | 32 - package-lock.json | 7066 +++++++++-------- package.json | 31 +- src/app/app-routing.module.ts | 61 +- src/app/app.component.spec.ts | 40 +- src/app/app.module.ts | 16 +- src/app/classes/external-references.ts | 75 +- src/app/classes/serializable.ts | 4 +- src/app/classes/stix/analytic.ts | 65 +- src/app/classes/stix/asset.ts | 9 +- src/app/classes/stix/campaign.ts | 17 +- src/app/classes/stix/collection.ts | 6 +- src/app/classes/stix/data-component.ts | 28 +- src/app/classes/stix/data-source.ts | 9 +- src/app/classes/stix/detection-strategy.ts | 9 +- src/app/classes/stix/group.ts | 9 +- src/app/classes/stix/identity.ts | 14 +- src/app/classes/stix/marking-definition.ts | 12 +- src/app/classes/stix/matrix.ts | 9 +- src/app/classes/stix/mitigation.ts | 9 +- src/app/classes/stix/note.ts | 6 +- src/app/classes/stix/relationship.ts | 26 +- src/app/classes/stix/software.ts | 9 +- src/app/classes/stix/stix-object.ts | 436 +- src/app/classes/stix/tactic.ts | 10 +- src/app/classes/stix/technique.ts | 14 +- src/app/classes/version-number.ts | 10 +- .../add-dialog/add-dialog.component.spec.ts | 14 + .../add-relationship-button.component.spec.ts | 15 + .../breadcrumb/breadcrumb.component.spec.ts | 2 + ...ollection-import-summary.component.spec.ts | 22 + ...collection-update-dialog.component.spec.ts | 27 +- .../confirmation-dialog.component.spec.ts | 12 + .../contributor-edit-dialog.component.spec.ts | 14 + .../create-new-dialog.component.spec.ts | 15 + .../delete-dialog.component.spec.ts | 10 + .../empty-list-marker.component.spec.ts | 2 + .../footer/footer.component.spec.ts | 2 + .../components/header/header.component.scss | 2 +- .../header/header.component.spec.ts | 40 + .../icon-view/icon-view.component.spec.ts | 7 + .../loading-overlay.component.html | 31 +- .../loading-overlay.component.scss | 44 + .../loading-overlay.component.spec.ts | 2 + .../loading-overlay.component.ts | 10 + .../markdown-view-dialog.component.spec.ts | 12 + .../tactic-cell/tactic-cell.component.spec.ts | 10 + .../technique-cell.component.spec.ts | 11 + .../multiple-choice-dialog.component.spec.ts | 13 + .../object-status.component.spec.ts | 38 + .../object-status/object-status.component.ts | 26 +- ...outdated-content-warning.component.spec.ts | 2 + .../property-diff.component.spec.ts | 7 + .../recent-activity.component.spec.ts | 5 + .../reference-edit-dialog.component.spec.ts | 34 + .../history-timeline.component.spec.ts | 48 + .../notes-editor.component.spec.ts | 24 + .../reference-sidebar.component.spec.ts | 18 + .../resources-drawer.component.spec.ts | 11 + .../search/search.component.spec.ts | 26 + .../save-dialog/save-dialog.component.html | 10 +- .../save-dialog/save-dialog.component.spec.ts | 28 + .../save-dialog/save-dialog.component.ts | 34 +- .../stix-json-dialog.component.spec.ts | 19 + .../alias-diff/alias-diff.component.spec.ts | 4 +- .../alias-edit-dialog.component.spec.ts | 8 +- .../alias-edit/alias-edit.component.spec.ts | 4 +- .../alias-property.component.spec.ts | 10 + .../alias-view/alias-view.component.spec.ts | 4 +- .../attackid-diff.component.html | 8 - .../attackid-diff.component.spec.ts | 22 - .../attackid-diff/attackid-diff.component.ts | 18 - .../attackid-edit.component.html | 23 - .../attackid-edit.component.scss | 8 - .../attackid-edit.component.spec.ts | 24 - .../attackid-edit/attackid-edit.component.ts | 75 - .../attackid-property.component.html | 61 +- .../attackid-property.component.spec.ts | 19 +- .../attackid-property.component.ts | 21 +- .../attackid-view.component.html | 3 - .../attackid-view.component.spec.ts | 24 - .../attackid-view/attackid-view.component.ts | 20 - .../boolean-property.component.spec.ts | 10 + .../citation-diff.component.spec.ts | 11 + .../citation-edit.component.spec.ts | 28 +- .../citation-property.component.spec.ts | 9 + .../citation-view.component.spec.ts | 12 +- .../datepicker-property.component.spec.ts | 9 + .../descriptive-diff.component.spec.ts | 10 +- .../descriptive-edit.component.spec.ts | 28 +- .../descriptive-property.component.spec.ts | 8 + .../descriptive-view.component.spec.ts | 15 + .../dictionary-diff.component.html | 23 + .../dictionary-diff.component.scss} | 0 .../dictionary-diff.component.ts | 86 + .../dictionary-edit.component.html | 81 + .../dictionary-edit.component.scss | 131 + .../dictionary-edit.component.ts | 114 + .../dictionary-property.component.html | 11 + .../dictionary-property.component.scss | 0 .../dictionary-property.component.ts | 36 + .../dictionary-view.component.html | 20 + .../dictionary-view.component.scss | 9 + .../dictionary-view.component.ts | 40 + ...external-references-diff.component.spec.ts | 11 +- ...rnal-references-property.component.spec.ts | 7 + ...external-references-view.component.spec.ts | 23 +- .../identity-property.component.spec.ts | 22 + .../list-diff/list-diff.component.spec.ts | 8 +- .../list-edit/list-edit.component.spec.ts | 13 +- .../list-property.component.spec.ts | 16 + .../list-view/list-view.component.spec.ts | 9 + .../list-view/list-view.component.ts | 9 +- ...-source-reference-dialog.component.spec.ts | 28 +- ...og-source-reference-diff.component.spec.ts | 20 +- ...og-source-reference-edit.component.spec.ts | 21 + ...ource-reference-property.component.spec.ts | 6 + ...og-source-reference-view.component.spec.ts | 17 +- .../name-property.component.spec.ts | 14 + .../ordered-list-diff.component.spec.ts | 11 + .../ordered-list-edit.component.spec.ts | 14 +- .../ordered-list-property.component.spec.ts | 9 + .../ordered-list-view.component.spec.ts | 11 + .../statement-diff.component.spec.ts | 12 +- .../statement-edit.component.spec.ts | 12 +- .../statement-property.component.spec.ts | 16 + .../statement-view.component.spec.ts | 10 + .../stix/stix-list/stix-list.component.html | 177 +- .../stix-list/stix-list.component.spec.ts | 11 +- .../stix/stix-list/stix-list.component.ts | 811 +- .../stixid-property.component.spec.ts | 11 + .../string-property.component.spec.ts | 15 + .../subtype-dialog.component.spec.ts | 38 +- .../subtype-diff.component.spec.ts | 10 +- .../subtype-edit.component.spec.ts | 9 +- .../subtype-property.component.spec.ts | 12 + .../subtype-view/subtype-view.component.html | 2 +- .../subtype-view/subtype-view.component.scss | 25 +- .../subtype-view.component.spec.ts | 11 + .../timestamp-property.component.spec.ts | 9 + .../timestamp-view.component.spec.ts | 11 +- .../tlp-diff/tlp-diff.component.spec.ts | 7 + .../tlp-edit/tlp-edit.component.spec.ts | 2 + .../tlp-property.component.spec.ts | 21 + .../tlp-view/tlp-view.component.spec.ts | 2 + .../version-diff.component.spec.ts | 7 + .../version-edit.component.spec.ts | 9 +- .../version-property.component.spec.ts | 11 +- .../version-view.component.spec.ts | 13 + .../subheading/subheading.component.spec.ts | 18 + .../toolbar/toolbar.component.spec.ts | 15 + .../users-list/users-list.component.spec.ts | 31 + .../validation-results.component.spec.ts | 7 + .../version-popover.component.spec.ts | 9 + .../authentication.service.spec.ts | 7 +- .../rest-api-connector.service.spec.ts | 7 +- .../rest-api/rest-api-connector.service.ts | 191 + .../services/editor/editor.service.spec.ts | 18 +- src/app/services/editor/editor.service.ts | 2 +- .../services/helpers/auth.interceptor.spec.ts | 2 + .../helpers/authorization.guard.spec.ts | 7 +- .../helpers/breadcrumb.service.spec.ts | 5 +- .../services/sidebar/sidebar.service.spec.ts | 9 +- src/app/services/title/title.service.spec.ts | 17 +- .../website-integration.service.spec.ts | 18 +- .../mocks/authentication-service.mock.ts | 145 + .../testing/mocks/rest-api-connector.mock.ts | 62 + src/app/utils/types.ts | 7 +- .../collection-manager.component.spec.ts | 18 + .../contributors-page.component.spec.ts | 19 + .../dashboard-page.component.html | 40 +- .../dashboard-page.component.spec.ts | 18 + .../dashboard-page.component.ts | 15 +- .../data-quality/data-quality.component.html | 79 + .../data-quality/data-quality.component.scss | 52 + .../data-quality.component.spec.ts | 31 + .../data-quality/data-quality.component.ts | 285 + ...ault-marking-definitions.component.spec.ts | 32 +- .../org-settings-page.component.spec.ts | 18 + .../teams-list-page.component.spec.ts | 29 +- .../teams-view-page.component.spec.ts | 33 +- .../user-accounts-page.component.spec.ts | 19 + .../help-page/help-page.component.spec.ts | 22 + .../landing-page.component.spec.ts | 5 + .../notes-page/notes-page.component.spec.ts | 4 + .../profile-page.component.spec.ts | 33 + .../reference-manager.component.spec.ts | 18 + .../analytic-view.component.html | 2 - .../analytic-view.component.spec.ts | 19 +- .../analytic-view/analytic-view.component.ts | 21 - .../asset-view/asset-view.component.spec.ts | 18 +- .../campaign-view.component.spec.ts | 18 +- .../collection-import-error.component.spec.ts | 16 + ...collection-import-review.component.spec.ts | 31 +- .../collection-import.component.html | 2 +- .../collection-import.component.spec.ts | 31 +- .../collection-import.component.ts | 227 +- .../collection-index-import.component.spec.ts | 31 +- .../collection-index-list.component.spec.ts | 18 +- .../collection-index-view.component.spec.ts | 24 +- .../collection-list.component.spec.ts | 8 +- .../collection-view.component.spec.ts | 25 +- .../data-component-view.component.html | 18 +- .../data-component-view.component.spec.ts | 18 +- .../data-source-view.component.spec.ts | 19 + .../detection-strategy-view.component.spec.ts | 20 +- .../group-view/group-view.component.spec.ts | 18 +- .../marking-definition-view.component.spec.ts | 12 + .../matrix-flat/matrix-flat.component.spec.ts | 2 + .../matrix-side/matrix-side.component.spec.ts | 2 + .../matrix-view/matrix-view.component.spec.ts | 33 +- .../mitigation-view.component.spec.ts | 18 +- .../relationship-view.component.spec.ts | 24 +- .../software-view.component.spec.ts | 23 + .../stix-dialog/stix-dialog.component.spec.ts | 35 +- .../stix-list-page.component.spec.ts | 5 +- .../stix-page/stix-page.component.spec.ts | 44 +- .../tactic-view/tactic-view.component.spec.ts | 19 +- .../technique-view.component.spec.ts | 27 +- src/style/colors.scss | 4 + src/style/layouts/app-table.scss | 26 +- src/style/layouts/extended-button.scss | 25 + src/style/theme.scss | 2 +- src/test-setup.ts | 27 + src/test.ts | 17 - tsconfig.spec.json | 8 +- vite.config.ts | 45 + 232 files changed, 8839 insertions(+), 4845 deletions(-) delete mode 100644 karma.conf.js delete mode 100644 src/app/components/stix/attackid-property/attackid-diff/attackid-diff.component.html delete mode 100644 src/app/components/stix/attackid-property/attackid-diff/attackid-diff.component.spec.ts delete mode 100644 src/app/components/stix/attackid-property/attackid-diff/attackid-diff.component.ts delete mode 100644 src/app/components/stix/attackid-property/attackid-edit/attackid-edit.component.html delete mode 100644 src/app/components/stix/attackid-property/attackid-edit/attackid-edit.component.scss delete mode 100644 src/app/components/stix/attackid-property/attackid-edit/attackid-edit.component.spec.ts delete mode 100644 src/app/components/stix/attackid-property/attackid-edit/attackid-edit.component.ts delete mode 100644 src/app/components/stix/attackid-property/attackid-view/attackid-view.component.html delete mode 100644 src/app/components/stix/attackid-property/attackid-view/attackid-view.component.spec.ts delete mode 100644 src/app/components/stix/attackid-property/attackid-view/attackid-view.component.ts create mode 100644 src/app/components/stix/dictionary-property/dictionary-diff/dictionary-diff.component.html rename src/app/components/stix/{attackid-property/attackid-view/attackid-view.component.scss => dictionary-property/dictionary-diff/dictionary-diff.component.scss} (100%) create mode 100644 src/app/components/stix/dictionary-property/dictionary-diff/dictionary-diff.component.ts create mode 100644 src/app/components/stix/dictionary-property/dictionary-edit/dictionary-edit.component.html create mode 100644 src/app/components/stix/dictionary-property/dictionary-edit/dictionary-edit.component.scss create mode 100644 src/app/components/stix/dictionary-property/dictionary-edit/dictionary-edit.component.ts create mode 100644 src/app/components/stix/dictionary-property/dictionary-property.component.html create mode 100644 src/app/components/stix/dictionary-property/dictionary-property.component.scss create mode 100644 src/app/components/stix/dictionary-property/dictionary-property.component.ts create mode 100644 src/app/components/stix/dictionary-property/dictionary-view/dictionary-view.component.html create mode 100644 src/app/components/stix/dictionary-property/dictionary-view/dictionary-view.component.scss create mode 100644 src/app/components/stix/dictionary-property/dictionary-view/dictionary-view.component.ts create mode 100644 src/app/testing/mocks/authentication-service.mock.ts create mode 100644 src/app/testing/mocks/rest-api-connector.mock.ts create mode 100644 src/app/views/dashboard-page/data-quality/data-quality.component.html create mode 100644 src/app/views/dashboard-page/data-quality/data-quality.component.scss create mode 100644 src/app/views/dashboard-page/data-quality/data-quality.component.spec.ts create mode 100644 src/app/views/dashboard-page/data-quality/data-quality.component.ts create mode 100644 src/test-setup.ts delete mode 100644 src/test.ts create mode 100644 vite.config.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4780a57b0..ad3f89835 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,6 @@ on: - next-major - beta - alpha - - adm - '*.x' # Matches branches like '1.x', '2.x' pull_request: # Run on all PRs regardless of target branch workflow_dispatch: @@ -36,9 +35,66 @@ jobs: - name: Validate all commits run: npx commitlint --from ${{ github.event.pull_request.base.sha || github.event.before }} --to ${{ github.event.pull_request.head.sha || github.sha }} --verbose - # Job 2: Semantic Release and Docker Build - release: + # Job 2a: Static Checks (Linting) + static-checks: + needs: [commitlint] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '22.x' + + - name: Install dependencies + run: npm ci + + # since there are lots of lint errors, run lint with warnonly to not fail the build + - name: Run linting + run: npm run lint:warnonly + + # Job 2b: Tests + run-tests: + runs-on: ubuntu-latest needs: [commitlint] + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '22.x' + + - name: Install dependencies + run: npm ci + + - name: Run regression tests with code coverage + run: npm run test:coverage + + - name: Upload coverage artifact + uses: actions/upload-artifact@v4 + with: + name: workbench-frontend-coverage + path: coverage + retention-days: 7 + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + slug: center-for-threat-informed-defense/attack-workbench-frontend + files: coverage/lcov.info + verbose: true + + - name: Verify integrity of dependencies + run: npm audit signatures + + # Job 3: Semantic Release and Docker Build + release: + needs: [static-checks, run-tests] # Only run on pushes (not PRs) - semantic-release will determine whether to release if: github.event_name == 'push' runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 6dd5bd968..62b926dd1 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ speed-measure-plugin*.json /.idea .project .classpath +.angular/ .c9/ *.launch .settings/ diff --git a/.releaserc b/.releaserc index 522f41539..0335eeded 100644 --- a/.releaserc +++ b/.releaserc @@ -11,10 +11,6 @@ { "name": "alpha", "prerelease": true - }, - { - "name": "adm", - "prerelease": true } ], "plugins": [ diff --git a/Dockerfile b/Dockerfile index ac95fadcd..8a3a01dcc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,6 +36,9 @@ LABEL org.opencontainers.image.title="ATT&CK Workbench Frontend Service" \ org.opencontainers.image.vendor="The MITRE Corporation" \ org.opencontainers.image.licenses="Apache-2.0" \ org.opencontainers.image.authors="MITRE ATT&CK" \ + org.opencontainers.image.version="${VERSION}" \ + org.opencontainers.image.created="${BUILDTIME}" \ + org.opencontainers.image.revision="${REVISION}" \ maintainer="MITRE ATT&CK" # Remove the default nginx website diff --git a/angular.json b/angular.json index 0734e1a67..f1ddc95f5 100644 --- a/angular.json +++ b/angular.json @@ -110,21 +110,7 @@ } }, "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "src/style/theme.scss" - ], - "scripts": [] - } + "builder": "@analogjs/vitest-angular:test" }, "e2e": { "builder": "@angular-devkit/build-angular:protractor", diff --git a/karma.conf.js b/karma.conf.js deleted file mode 100644 index c11180d94..000000000 --- a/karma.conf.js +++ /dev/null @@ -1,32 +0,0 @@ -// Karma configuration file, see link for more information -// https://karma-runner.github.io/1.0/config/configuration-file.html - -module.exports = function (config) { - config.set({ - basePath: '', - frameworks: ['jasmine', '@angular-devkit/build-angular'], - plugins: [ - require('karma-jasmine'), - require('karma-chrome-launcher'), - require('karma-jasmine-html-reporter'), - require('karma-coverage-istanbul-reporter'), - require('@angular-devkit/build-angular/plugins/karma') - ], - client: { - clearContext: false // leave Jasmine Spec Runner output visible in browser - }, - coverageIstanbulReporter: { - dir: require('path').join(__dirname, './coverage/app'), - reports: ['html', 'lcovonly', 'text-summary'], - fixWebpackSourcePaths: true - }, - reporters: ['progress', 'kjhtml'], - port: 9876, - colors: true, - logLevel: config.LOG_INFO, - autoWatch: true, - browsers: ['Chrome'], - singleRun: false, - restartOnFileChange: true - }); -}; diff --git a/package-lock.json b/package-lock.json index 09976baa3..43109e488 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,9 +20,11 @@ "@angular/platform-browser": "^19.2.19", "@angular/platform-browser-dynamic": "^19.2.19", "@angular/router": "^19.2.19", + "@mitre-attack/attack-data-model": "^5.0.0-beta.1", "@ng-matero/extensions": "^19.5.1", "diff-match-patch-ts": "^0.8.0", "jdenticon": "^3.2.0", + "lodash": "^4.17.21", "marked": "^15.0.11", "moment": "^2.29.4", "ngx-autosize": "^2.0.4", @@ -36,16 +38,18 @@ "zone.js": "~0.15.0" }, "devDependencies": { + "@analogjs/vite-plugin-angular": "^1.22.1", + "@analogjs/vitest-angular": "^1.22.1", "@angular-devkit/build-angular": "^19.2.22", "@angular/cli": "^19.2.22", "@angular/compiler-cli": "^19.2.19", "@codedependant/semantic-release-docker": "^5.1.1", "@commitlint/config-conventional": "^20.4.3", - "@types/jasmine": "^6.0.0", - "@types/jasminewd2": "^2.0.10", + "@eslint/js": "^9.39.1", "@types/node": "^20.12.11", + "@vitest/coverage-v8": "^3.2.4", + "@vitest/ui": "^3.2.4", "angular-eslint": "^19.4.0", - "codelyzer": "^6.0.0", "commitlint": "^19.8.0", "esbuild-visualizer": "^0.7.0", "eslint": "^9.23.0", @@ -53,22 +57,25 @@ "eslint-plugin-prettier": "^5.5.5", "eslint-plugin-unused-imports": "^4.1.4", "husky": "^9.1.7", - "jasmine-core": "^5.1.1", - "jasmine-spec-reporter": "^7.0.0", - "karma": "~6.4.2", - "karma-chrome-launcher": "^3.2.0", - "karma-coverage-istanbul-reporter": "~3.0.2", - "karma-jasmine": "^5.1.0", - "karma-jasmine-html-reporter": "^2.1.0", + "jsdom": "^27.0.1", "prettier": "^3.8.1", "prettier-eslint": "^16.3.0", + "sass": "^1.94.2", "semantic-release": "^25.0.3", "ts-node": "^10.9.1", - "tslint": "~6.1.0", "typescript": "^5.8.3", - "typescript-eslint": "^8.53.1" + "typescript-eslint": "^8.53.1", + "vite": "^6.4.1", + "vitest": "^3.2.4" } }, + "node_modules/@acemir/cssom": { + "version": "0.9.31", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", + "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==", + "dev": true, + "license": "MIT" + }, "node_modules/@actions/core": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz", @@ -122,30 +129,64 @@ "node": ">=6.0.0" } }, - "node_modules/@angular-devkit/architect": { - "version": "0.1902.22", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1902.22.tgz", - "integrity": "sha512-98NCVZZwDoL1oW0aj9Wd7piIkt/oSjMZEI04f3oL5nGD1LoXcN7BrYKXIpZ4NP4Pfn+7gJo3nrhglHnhcwQXyg==", + "node_modules/@analogjs/vite-plugin-angular": { + "version": "1.22.5", + "resolved": "https://registry.npmjs.org/@analogjs/vite-plugin-angular/-/vite-plugin-angular-1.22.5.tgz", + "integrity": "sha512-N1BQD6HQSp2Imbb1fThymskWFSLq0ZF+d2fe3DgErwlBFf6SzRp++iFltddQc3wIzenTXE+5brS4fAPXO8UT9g==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "19.2.22", - "rxjs": "7.8.1" + "ts-morph": "^21.0.0", + "vfile": "^6.0.3" }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/brandonroberts" + }, + "peerDependencies": { + "@angular-devkit/build-angular": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0", + "@angular/build": "^18.0.0 || ^19.0.0 || ^20.0.0" + }, + "peerDependenciesMeta": { + "@angular-devkit/build-angular": { + "optional": true + }, + "@angular/build": { + "optional": true + } } }, - "node_modules/@angular-devkit/architect/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "node_modules/@analogjs/vitest-angular": { + "version": "1.22.5", + "resolved": "https://registry.npmjs.org/@analogjs/vitest-angular/-/vitest-angular-1.22.5.tgz", + "integrity": "sha512-lYwa9f6LFClW80sPhCydTOQvKnpWPnyChFhYdgMRpUZuKYo9102PndOLZkqR5K8WlKSilbP2293whaG1vZBMyA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/brandonroberts" + }, + "peerDependencies": { + "@analogjs/vite-plugin-angular": "*", + "@angular-devkit/architect": ">=0.1500.0 < 0.2100.0", + "vitest": "^1.3.1 || ^2.0.0 || ^3.0.0 || ^4.0.0" + } + }, + "node_modules/@angular-devkit/architect": { + "version": "0.2003.20", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2003.20.tgz", + "integrity": "sha512-1g7q37Aq4dvDdQDW0PtWXfiX5hBV78K74QUtFkAXGIXU3DkguwOQaqHILCnIRAVr0wFlWDckWVuO5OT6Cl9HeQ==", + "dev": true, + "license": "MIT", + "peer": true, "dependencies": { - "tslib": "^2.1.0" + "@angular-devkit/core": "20.3.20", + "rxjs": "7.8.2" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" } }, "node_modules/@angular-devkit/build-angular": { @@ -274,6 +315,50 @@ } } }, + "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/architect": { + "version": "0.1902.22", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1902.22.tgz", + "integrity": "sha512-98NCVZZwDoL1oW0aj9Wd7piIkt/oSjMZEI04f3oL5nGD1LoXcN7BrYKXIpZ4NP4Pfn+7gJo3nrhglHnhcwQXyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "19.2.22", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core": { + "version": "19.2.22", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.22.tgz", + "integrity": "sha512-OqN/Ded+ZKypPZN5+qUFwtnKGl7FKpxJXYO2Vts5vLBojY5goCZd9SGW1CyXeuPnisRUW+vjqBQbWYuEUh36Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.18.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.2", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^4.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, "node_modules/@angular-devkit/build-angular/node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -284,6 +369,37 @@ "tslib": "^2.1.0" } }, + "node_modules/@angular-devkit/build-angular/node_modules/sass": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.85.0.tgz", + "integrity": "sha512-3ToiC1xZ1Y8aU7+CkgCI/tqyuPXEmYGJXO7H4uqp0xkLXUqp88rQQ4j1HmP37xSJLbCJPaIiv+cT1y+grssrww==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, "node_modules/@angular-devkit/build-webpack": { "version": "0.1902.22", "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1902.22.tgz", @@ -304,17 +420,23 @@ "webpack-dev-server": "^5.0.2" } }, - "node_modules/@angular-devkit/build-webpack/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/architect": { + "version": "0.1902.22", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1902.22.tgz", + "integrity": "sha512-98NCVZZwDoL1oW0aj9Wd7piIkt/oSjMZEI04f3oL5nGD1LoXcN7BrYKXIpZ4NP4Pfn+7gJo3nrhglHnhcwQXyg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "tslib": "^2.1.0" + "@angular-devkit/core": "19.2.22", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" } }, - "node_modules/@angular-devkit/core": { + "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/core": { "version": "19.2.22", "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.22.tgz", "integrity": "sha512-OqN/Ded+ZKypPZN5+qUFwtnKGl7FKpxJXYO2Vts5vLBojY5goCZd9SGW1CyXeuPnisRUW+vjqBQbWYuEUh36Tw==", @@ -342,7 +464,7 @@ } } }, - "node_modules/@angular-devkit/core/node_modules/rxjs": { + "node_modules/@angular-devkit/build-webpack/node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", @@ -352,6 +474,59 @@ "tslib": "^2.1.0" } }, + "node_modules/@angular-devkit/build-webpack/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@angular-devkit/core": { + "version": "20.3.20", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.20.tgz", + "integrity": "sha512-Iobw7He3yJVR2aQ6JN9Kq/2ldD8+uHzJZwd41SQ91A+TzPrBRSV0t80WHHrANZ7xnAjtHDc7zSSGp/i7DzUc9g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ajv": "8.18.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.3", + "rxjs": "7.8.2", + "source-map": "0.7.6" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^4.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/core/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@angular-devkit/schematics": { "version": "19.2.22", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.22.tgz", @@ -371,6 +546,34 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": { + "version": "19.2.22", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.22.tgz", + "integrity": "sha512-OqN/Ded+ZKypPZN5+qUFwtnKGl7FKpxJXYO2Vts5vLBojY5goCZd9SGW1CyXeuPnisRUW+vjqBQbWYuEUh36Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.18.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.2", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^4.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, "node_modules/@angular-devkit/schematics/node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -381,6 +584,16 @@ "tslib": "^2.1.0" } }, + "node_modules/@angular-devkit/schematics/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, "node_modules/@angular-eslint/builder": { "version": "19.8.1", "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-19.8.1.tgz", @@ -396,43 +609,107 @@ "typescript": "*" } }, - "node_modules/@angular-eslint/bundled-angular-compiler": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-19.8.1.tgz", - "integrity": "sha512-WXi1YbSs7SIQo48u+fCcc5Nt14/T4QzYQPLZUnjtsUXPgQG7ZoahhcGf7PPQ+n0V3pSopHOlSHwqK+tSsYK87A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@angular-eslint/eslint-plugin": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-19.8.1.tgz", - "integrity": "sha512-wZEBMPwD2TRhifG751hcj137EMIEaFmsxRB2EI+vfINCgPnFGSGGOHXqi8aInn9fXqHs7VbXkAzXYdBsvy1m4Q==", + "node_modules/@angular-eslint/builder/node_modules/@angular-devkit/architect": { + "version": "0.1902.22", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1902.22.tgz", + "integrity": "sha512-98NCVZZwDoL1oW0aj9Wd7piIkt/oSjMZEI04f3oL5nGD1LoXcN7BrYKXIpZ4NP4Pfn+7gJo3nrhglHnhcwQXyg==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "19.8.1", - "@angular-eslint/utils": "19.8.1" + "@angular-devkit/core": "19.2.22", + "rxjs": "7.8.1" }, - "peerDependencies": { - "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": "*" + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" } }, - "node_modules/@angular-eslint/eslint-plugin-template": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-19.8.1.tgz", - "integrity": "sha512-0ZVQldndLrDfB0tzFe/uIwvkUcakw8qGxvkEU0l7kSbv/ngNQ/qrkRi7P64otB15inIDUNZI2jtmVat52dqSfQ==", + "node_modules/@angular-eslint/builder/node_modules/@angular-devkit/core": { + "version": "19.2.22", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.22.tgz", + "integrity": "sha512-OqN/Ded+ZKypPZN5+qUFwtnKGl7FKpxJXYO2Vts5vLBojY5goCZd9SGW1CyXeuPnisRUW+vjqBQbWYuEUh36Tw==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "19.8.1", - "@angular-eslint/utils": "19.8.1", - "aria-query": "5.3.2", - "axobject-query": "4.1.0" + "ajv": "8.18.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.2", + "rxjs": "7.8.1", + "source-map": "0.7.4" }, - "peerDependencies": { - "@angular-eslint/template-parser": "19.8.1", + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^4.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-eslint/builder/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@angular-eslint/builder/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@angular-eslint/bundled-angular-compiler": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-19.8.1.tgz", + "integrity": "sha512-WXi1YbSs7SIQo48u+fCcc5Nt14/T4QzYQPLZUnjtsUXPgQG7ZoahhcGf7PPQ+n0V3pSopHOlSHwqK+tSsYK87A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@angular-eslint/eslint-plugin": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-19.8.1.tgz", + "integrity": "sha512-wZEBMPwD2TRhifG751hcj137EMIEaFmsxRB2EI+vfINCgPnFGSGGOHXqi8aInn9fXqHs7VbXkAzXYdBsvy1m4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "19.8.1", + "@angular-eslint/utils": "19.8.1" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/eslint-plugin-template": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-19.8.1.tgz", + "integrity": "sha512-0ZVQldndLrDfB0tzFe/uIwvkUcakw8qGxvkEU0l7kSbv/ngNQ/qrkRi7P64otB15inIDUNZI2jtmVat52dqSfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "19.8.1", + "@angular-eslint/utils": "19.8.1", + "aria-query": "5.3.2", + "axobject-query": "4.1.0" + }, + "peerDependencies": { + "@angular-eslint/template-parser": "19.8.1", "@typescript-eslint/types": "^7.11.0 || ^8.0.0", "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0", @@ -455,6 +732,44 @@ "strip-json-comments": "3.1.1" } }, + "node_modules/@angular-eslint/schematics/node_modules/@angular-devkit/core": { + "version": "19.2.22", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.22.tgz", + "integrity": "sha512-OqN/Ded+ZKypPZN5+qUFwtnKGl7FKpxJXYO2Vts5vLBojY5goCZd9SGW1CyXeuPnisRUW+vjqBQbWYuEUh36Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.18.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.2", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^4.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-eslint/schematics/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/@angular-eslint/schematics/node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -468,6 +783,16 @@ "node": ">=10" } }, + "node_modules/@angular-eslint/schematics/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, "node_modules/@angular-eslint/template-parser": { "version": "19.8.1", "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-19.8.1.tgz", @@ -600,6 +925,91 @@ } } }, + "node_modules/@angular/build/node_modules/@angular-devkit/architect": { + "version": "0.1902.22", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1902.22.tgz", + "integrity": "sha512-98NCVZZwDoL1oW0aj9Wd7piIkt/oSjMZEI04f3oL5nGD1LoXcN7BrYKXIpZ4NP4Pfn+7gJo3nrhglHnhcwQXyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "19.2.22", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/build/node_modules/@angular-devkit/core": { + "version": "19.2.22", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.22.tgz", + "integrity": "sha512-OqN/Ded+ZKypPZN5+qUFwtnKGl7FKpxJXYO2Vts5vLBojY5goCZd9SGW1CyXeuPnisRUW+vjqBQbWYuEUh36Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.18.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.2", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^4.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular/build/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@angular/build/node_modules/sass": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.85.0.tgz", + "integrity": "sha512-3ToiC1xZ1Y8aU7+CkgCI/tqyuPXEmYGJXO7H4uqp0xkLXUqp88rQQ4j1HmP37xSJLbCJPaIiv+cT1y+grssrww==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/@angular/build/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, "node_modules/@angular/cdk": { "version": "19.2.19", "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-19.2.19.tgz", @@ -649,6 +1059,70 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular/cli/node_modules/@angular-devkit/architect": { + "version": "0.1902.22", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1902.22.tgz", + "integrity": "sha512-98NCVZZwDoL1oW0aj9Wd7piIkt/oSjMZEI04f3oL5nGD1LoXcN7BrYKXIpZ4NP4Pfn+7gJo3nrhglHnhcwQXyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "19.2.22", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/cli/node_modules/@angular-devkit/core": { + "version": "19.2.22", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.22.tgz", + "integrity": "sha512-OqN/Ded+ZKypPZN5+qUFwtnKGl7FKpxJXYO2Vts5vLBojY5goCZd9SGW1CyXeuPnisRUW+vjqBQbWYuEUh36Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.18.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.2", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^4.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular/cli/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@angular/cli/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, "node_modules/@angular/common": { "version": "19.2.19", "resolved": "https://registry.npmjs.org/@angular/common/-/common-19.2.19.tgz", @@ -891,24 +1365,69 @@ "url": "https://github.com/sponsors/antfu" } }, - "node_modules/@antfu/utils": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-9.3.0.tgz", - "integrity": "sha512-9hFT4RauhcUzqOE4f1+frMKLZrgNog5b06I7VmZQV1BkvwvqrbC8EBZf3L1eEL2AKb6rNKjER0sEvJiSP1FXEA==", - "license": "MIT", - "optional": true, - "funding": { - "url": "https://github.com/sponsors/antfu" + "node_modules/@asamuzakjp/css-color": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.2.tgz", + "integrity": "sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.0.0", + "@csstools/css-color-parser": "^4.0.1", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.2.5" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz", + "integrity": "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.6" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" } }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -917,9 +1436,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", - "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "dev": true, "license": "MIT", "engines": { @@ -1005,13 +1524,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.2", + "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", @@ -1032,18 +1551,18 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz", - "integrity": "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.5", + "@babel/traverse": "^7.28.6", "semver": "^6.3.1" }, "engines": { @@ -1118,22 +1637,43 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", - "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.7.tgz", + "integrity": "sha512-6Fqi8MtQ/PweQ9xvux65emkLQ83uB+qAVtfHkC9UodyHMIZdxNI01HjLCLUtybElp2KY2XNE0nOgyP1E1vXw9w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "debug": "^4.4.1", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", "lodash.debounce": "^4.0.8", - "resolve": "^1.22.10" + "resolve": "^1.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/@babel/helper-globals": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", @@ -1159,29 +1699,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1204,9 +1744,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true, "license": "MIT", "engines": { @@ -1245,15 +1785,15 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", - "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1320,42 +1860,42 @@ } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz", - "integrity": "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", + "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.3", - "@babel/types": "^7.28.2" + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -1432,14 +1972,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", - "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", + "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1462,13 +2002,13 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", - "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", + "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1579,13 +2119,13 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz", - "integrity": "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", + "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1595,14 +2135,14 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", - "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", + "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1612,14 +2152,14 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", - "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", + "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1629,18 +2169,18 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", - "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", + "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-globals": "^7.28.0", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.28.4" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1663,14 +2203,14 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", - "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", + "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/template": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1697,14 +2237,14 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", - "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", + "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1730,14 +2270,14 @@ } }, "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1763,13 +2303,13 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz", - "integrity": "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", + "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1830,13 +2370,13 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", - "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", + "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1862,13 +2402,13 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz", - "integrity": "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", + "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1911,14 +2451,14 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", - "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1928,16 +2468,16 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", - "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", + "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.5" + "@babel/traverse": "^7.29.0" }, "engines": { "node": ">=6.9.0" @@ -1964,14 +2504,14 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1997,13 +2537,13 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", - "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", + "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -2013,13 +2553,13 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", - "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", + "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -2029,17 +2569,17 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz", - "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", + "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/traverse": "^7.28.4" + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -2066,13 +2606,13 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", - "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", + "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -2082,13 +2622,13 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz", - "integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", + "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { @@ -2115,14 +2655,14 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", - "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", + "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -2132,15 +2672,15 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", - "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", + "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -2179,13 +2719,13 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz", - "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", + "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -2195,14 +2735,14 @@ } }, "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", - "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", + "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -2275,13 +2815,13 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", - "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", + "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { @@ -2356,14 +2896,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", - "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", + "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -2390,14 +2930,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", - "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", + "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -2529,33 +3069,33 @@ } }, "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -2563,14 +3103,14 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -2580,9 +3120,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { @@ -2593,54 +3133,64 @@ "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@braintree/sanitize-url": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.1.tgz", - "integrity": "sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", + "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==", "license": "MIT", "optional": true }, "node_modules/@chevrotain/cst-dts-gen": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.1.tgz", - "integrity": "sha512-fRHyv6/f542qQqiRGalrfJl/evD39mAvbJLCekPazhiextEatq1Jx1K/i9gSd5NNO0ds03ek0Cbo/4uVKmOBcw==", + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.2.tgz", + "integrity": "sha512-XTsjvDVB5nDZBQB8o0o/0ozNelQtn2KrUVteIHSlPd2VAV2utEb6JzyCJaJ8tGxACR4RiBNWy5uYUHX2eji88Q==", "license": "Apache-2.0", "optional": true, "dependencies": { - "@chevrotain/gast": "11.1.1", - "@chevrotain/types": "11.1.1", + "@chevrotain/gast": "11.1.2", + "@chevrotain/types": "11.1.2", "lodash-es": "4.17.23" } }, "node_modules/@chevrotain/gast": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.1.1.tgz", - "integrity": "sha512-Ko/5vPEYy1vn5CbCjjvnSO4U7GgxyGm+dfUZZJIWTlQFkXkyym0jFYrWEU10hyCjrA7rQtiHtBr0EaZqvHFZvg==", + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.1.2.tgz", + "integrity": "sha512-Z9zfXR5jNZb1Hlsd/p+4XWeUFugrHirq36bKzPWDSIacV+GPSVXdk+ahVWZTwjhNwofAWg/sZg58fyucKSQx5g==", "license": "Apache-2.0", "optional": true, "dependencies": { - "@chevrotain/types": "11.1.1", + "@chevrotain/types": "11.1.2", "lodash-es": "4.17.23" } }, "node_modules/@chevrotain/regexp-to-ast": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.1.1.tgz", - "integrity": "sha512-ctRw1OKSXkOrR8VTvOxrQ5USEc4sNrfwXHa1NuTcR7wre4YbjPcKw+82C2uylg/TEwFRgwLmbhlln4qkmDyteg==", + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.1.2.tgz", + "integrity": "sha512-nMU3Uj8naWer7xpZTYJdxbAs6RIv/dxYzkYU8GSwgUtcAAlzjcPfX1w+RKRcYG8POlzMeayOQ/znfwxEGo5ulw==", "license": "Apache-2.0", "optional": true }, "node_modules/@chevrotain/types": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.1.tgz", - "integrity": "sha512-wb2ToxG8LkgPYnKe9FH8oGn3TMCBdnwiuNC5l5y+CtlaVRbCytU0kbVsk6CGrqTL4ZN4ksJa0TXOYbxpbthtqw==", + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz", + "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==", "license": "Apache-2.0", "optional": true }, "node_modules/@chevrotain/utils": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.1.1.tgz", - "integrity": "sha512-71eTYMzYXYSFPrbg/ZwftSaSDld7UYlS8OQa3lNnn9jzNtpFbaReRRyghzqS7rI3CDaorqpPJJcXGHK+FE1TVQ==", + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.1.2.tgz", + "integrity": "sha512-4mudFAQ6H+MqBTfqLmU7G1ZwRzCLfJEooL/fsF6rCX5eePMbGhoy5n4g+G4vlh2muDcsCTJtL+uKbOzWxs5LHA==", "license": "Apache-2.0", "optional": true }, @@ -2666,6 +3216,7 @@ "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=0.1.90" } @@ -2692,73 +3243,57 @@ "node": ">=v18" } }, - "node_modules/@commitlint/config-conventional": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-20.4.3.tgz", - "integrity": "sha512-9RtLySbYQAs8yEqWEqhSZo9nYhbm57jx7qHXtgRmv/nmeQIjjMcwf6Dl+y5UZcGWgWx435TAYBURONaJIuCjWg==", + "node_modules/@commitlint/cli/node_modules/@commitlint/types": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.8.1.tgz", + "integrity": "sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^20.4.3", - "conventional-changelog-conventionalcommits": "^9.2.0" + "@types/conventional-commits-parser": "^5.0.0", + "chalk": "^5.3.0" }, "engines": { "node": ">=v18" } }, - "node_modules/@commitlint/config-conventional/node_modules/@commitlint/types": { + "node_modules/@commitlint/config-conventional": { "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-20.4.3.tgz", - "integrity": "sha512-51OWa1Gi6ODOasPmfJPq6js4pZoomima4XLZZCrkldaH2V5Nb3bVhNXPeT6XV0gubbainSpTw4zi68NqAeCNCg==", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-20.4.3.tgz", + "integrity": "sha512-9RtLySbYQAs8yEqWEqhSZo9nYhbm57jx7qHXtgRmv/nmeQIjjMcwf6Dl+y5UZcGWgWx435TAYBURONaJIuCjWg==", "dev": true, "license": "MIT", "dependencies": { - "conventional-commits-parser": "^6.3.0", - "picocolors": "^1.1.1" + "@commitlint/types": "^20.4.3", + "conventional-changelog-conventionalcommits": "^9.2.0" }, "engines": { "node": ">=v18" } }, - "node_modules/@commitlint/config-conventional/node_modules/conventional-commits-parser": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.3.0.tgz", - "integrity": "sha512-RfOq/Cqy9xV9bOA8N+ZH6DlrDR+5S3Mi0B5kACEjESpE+AviIpAptx9a9cFpWCCvgRtWT+0BbUw+e1BZfts9jg==", + "node_modules/@commitlint/config-validator": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-19.8.1.tgz", + "integrity": "sha512-0jvJ4u+eqGPBIzzSdqKNX1rvdbSU1lPNYlfQQRIFnBgLy26BtC0cFnr7c/AyuzExMxWsMOte6MkTi9I3SQ3iGQ==", "dev": true, "license": "MIT", "dependencies": { - "@simple-libs/stream-utils": "^1.2.0", - "meow": "^13.0.0" - }, - "bin": { - "conventional-commits-parser": "dist/cli/index.js" + "@commitlint/types": "^19.8.1", + "ajv": "^8.11.0" }, "engines": { - "node": ">=18" - } - }, - "node_modules/@commitlint/config-conventional/node_modules/meow": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", - "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=v18" } }, - "node_modules/@commitlint/config-validator": { + "node_modules/@commitlint/config-validator/node_modules/@commitlint/types": { "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-19.8.1.tgz", - "integrity": "sha512-0jvJ4u+eqGPBIzzSdqKNX1rvdbSU1lPNYlfQQRIFnBgLy26BtC0cFnr7c/AyuzExMxWsMOte6MkTi9I3SQ3iGQ==", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.8.1.tgz", + "integrity": "sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^19.8.1", - "ajv": "^8.11.0" + "@types/conventional-commits-parser": "^5.0.0", + "chalk": "^5.3.0" }, "engines": { "node": ">=v18" @@ -2782,6 +3317,20 @@ "node": ">=v18" } }, + "node_modules/@commitlint/ensure/node_modules/@commitlint/types": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.8.1.tgz", + "integrity": "sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/conventional-commits-parser": "^5.0.0", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" + } + }, "node_modules/@commitlint/execute-rule": { "version": "19.8.1", "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-19.8.1.tgz", @@ -2806,6 +3355,20 @@ "node": ">=v18" } }, + "node_modules/@commitlint/format/node_modules/@commitlint/types": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.8.1.tgz", + "integrity": "sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/conventional-commits-parser": "^5.0.0", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" + } + }, "node_modules/@commitlint/is-ignored": { "version": "19.8.1", "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-19.8.1.tgz", @@ -2820,6 +3383,20 @@ "node": ">=v18" } }, + "node_modules/@commitlint/is-ignored/node_modules/@commitlint/types": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.8.1.tgz", + "integrity": "sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/conventional-commits-parser": "^5.0.0", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" + } + }, "node_modules/@commitlint/lint": { "version": "19.8.1", "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-19.8.1.tgz", @@ -2836,6 +3413,20 @@ "node": ">=v18" } }, + "node_modules/@commitlint/lint/node_modules/@commitlint/types": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.8.1.tgz", + "integrity": "sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/conventional-commits-parser": "^5.0.0", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" + } + }, "node_modules/@commitlint/load": { "version": "19.8.1", "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-19.8.1.tgz", @@ -2858,19 +3449,33 @@ "node": ">=v18" } }, - "node_modules/@commitlint/message": { + "node_modules/@commitlint/load/node_modules/@commitlint/types": { "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-19.8.1.tgz", - "integrity": "sha512-+PMLQvjRXiU+Ae0Wc+p99EoGEutzSXFVwQfa3jRNUZLNW5odZAyseb92OSBTKCu+9gGZiJASt76Cj3dLTtcTdg==", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.8.1.tgz", + "integrity": "sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==", "dev": true, "license": "MIT", + "dependencies": { + "@types/conventional-commits-parser": "^5.0.0", + "chalk": "^5.3.0" + }, "engines": { "node": ">=v18" } }, - "node_modules/@commitlint/parse": { + "node_modules/@commitlint/message": { "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-19.8.1.tgz", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-19.8.1.tgz", + "integrity": "sha512-+PMLQvjRXiU+Ae0Wc+p99EoGEutzSXFVwQfa3jRNUZLNW5odZAyseb92OSBTKCu+9gGZiJASt76Cj3dLTtcTdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/parse": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-19.8.1.tgz", "integrity": "sha512-mmAHYcMBmAgJDKWdkjIGq50X4yB0pSGpxyOODwYmoexxxiUCy5JJT99t1+PEMK7KtsCtzuWYIAXYAiKR+k+/Jw==", "dev": true, "license": "MIT", @@ -2883,6 +3488,52 @@ "node": ">=v18" } }, + "node_modules/@commitlint/parse/node_modules/@commitlint/types": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.8.1.tgz", + "integrity": "sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/conventional-commits-parser": "^5.0.0", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/parse/node_modules/conventional-commits-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", + "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-text-path": "^2.0.0", + "JSONStream": "^1.3.5", + "meow": "^12.0.1", + "split2": "^4.0.0" + }, + "bin": { + "conventional-commits-parser": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@commitlint/parse/node_modules/meow": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", + "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@commitlint/read": { "version": "19.8.1", "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-19.8.1.tgz", @@ -2900,6 +3551,20 @@ "node": ">=v18" } }, + "node_modules/@commitlint/read/node_modules/@commitlint/types": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.8.1.tgz", + "integrity": "sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/conventional-commits-parser": "^5.0.0", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" + } + }, "node_modules/@commitlint/resolve-extends": { "version": "19.8.1", "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-19.8.1.tgz", @@ -2918,6 +3583,20 @@ "node": ">=v18" } }, + "node_modules/@commitlint/resolve-extends/node_modules/@commitlint/types": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.8.1.tgz", + "integrity": "sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/conventional-commits-parser": "^5.0.0", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" + } + }, "node_modules/@commitlint/rules": { "version": "19.8.1", "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-19.8.1.tgz", @@ -2934,6 +3613,20 @@ "node": ">=v18" } }, + "node_modules/@commitlint/rules/node_modules/@commitlint/types": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.8.1.tgz", + "integrity": "sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/conventional-commits-parser": "^5.0.0", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" + } + }, "node_modules/@commitlint/to-lines": { "version": "19.8.1", "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-19.8.1.tgz", @@ -2958,14 +3651,14 @@ } }, "node_modules/@commitlint/types": { - "version": "19.8.1", - "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.8.1.tgz", - "integrity": "sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==", + "version": "20.4.3", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-20.4.3.tgz", + "integrity": "sha512-51OWa1Gi6ODOasPmfJPq6js4pZoomima4XLZZCrkldaH2V5Nb3bVhNXPeT6XV0gubbainSpTw4zi68NqAeCNCg==", "dev": true, "license": "MIT", "dependencies": { - "@types/conventional-commits-parser": "^5.0.0", - "chalk": "^5.3.0" + "conventional-commits-parser": "^6.3.0", + "picocolors": "^1.1.1" }, "engines": { "node": ">=v18" @@ -2995,6 +3688,138 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@csstools/color-helpers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", + "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", + "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.1.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.0.tgz", + "integrity": "sha512-H4tuz2nhWgNKLt1inYpoVCfbJbMwX/lQKp3g69rrrIMIYlFD9+zTykOKhNR8uGrAmbS/kT9n6hTFkmDkxLgeTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0" + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, "node_modules/@ctrl/tinycolor": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz", @@ -3469,20 +4294,27 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", - "minimatch": "^3.1.2" + "minimatch": "^3.1.5" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/config-array/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, + "license": "MIT" + }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -3495,9 +4327,9 @@ } }, "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -3534,20 +4366,20 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.12.4", + "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, "engines": { @@ -3558,9 +4390,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -3574,6 +4406,13 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/@eslint/eslintrc/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, + "license": "MIT" + }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -3603,9 +4442,9 @@ "license": "MIT" }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -3616,9 +4455,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.0.tgz", - "integrity": "sha512-BIhe0sW91JGPiaF1mOuPy5v8NflqfjIcDNpC+LbW9f609WVRX1rArrhi6Z2ymvrAry9jw+5POTj4t2t62o8Bmw==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", "dev": true, "license": "MIT", "engines": { @@ -3652,6 +4491,24 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@exodus/bytes": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", + "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, "node_modules/@fastify/busboy": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", @@ -3702,6 +4559,13 @@ "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/config-array/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, + "license": "MIT" + }, "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -3714,9 +4578,9 @@ } }, "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -3770,39 +4634,21 @@ "optional": true }, "node_modules/@iconify/utils": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.0.2.tgz", - "integrity": "sha512-EfJS0rLfVuRuJRn4psJHtK2A9TqVnkxPpHY6lYHiB9+8eSuudsxbwMiavocG45ujOo6FJ+CIRlRnlOGinzkaGQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.0.tgz", + "integrity": "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==", "license": "MIT", "optional": true, "dependencies": { "@antfu/install-pkg": "^1.1.0", - "@antfu/utils": "^9.2.0", "@iconify/types": "^2.0.0", - "debug": "^4.4.1", - "globals": "^15.15.0", - "kolorist": "^1.8.0", - "local-pkg": "^1.1.1", - "mlly": "^1.7.4" - } - }, - "node_modules/@iconify/utils/node_modules/globals": { - "version": "15.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", - "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "mlly": "^1.8.0" } }, "node_modules/@inquirer/ansi": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.1.tgz", - "integrity": "sha512-yqq0aJW/5XPhi5xOAL1xRCpe1eh8UFVgYFpFsjEqmIR8rKLyP+HINvFXwUaxYICflJrVlxnp7lLN6As735kVpw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", "dev": true, "license": "MIT", "engines": { @@ -3810,17 +4656,17 @@ } }, "node_modules/@inquirer/checkbox": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.0.tgz", - "integrity": "sha512-5+Q3PKH35YsnoPTh75LucALdAxom6xh5D1oeY561x4cqBuH24ZFVyFREPe14xgnrtmGu3EEt1dIi60wRVSnGCw==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", + "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^1.0.1", - "@inquirer/core": "^10.3.0", - "@inquirer/figures": "^1.0.14", - "@inquirer/type": "^3.0.9", - "yoctocolors-cjs": "^2.1.2" + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" }, "engines": { "node": ">=18" @@ -3857,20 +4703,20 @@ } }, "node_modules/@inquirer/core": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.0.tgz", - "integrity": "sha512-Uv2aPPPSK5jeCplQmQ9xadnFx2Zhj9b5Dj7bU6ZeCdDNNY11nhYy4btcSdtDguHqCT2h5oNeQTcUNSGGLA7NTA==", + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^1.0.1", - "@inquirer/figures": "^1.0.14", - "@inquirer/type": "^3.0.9", + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", "cli-width": "^4.1.0", "mute-stream": "^2.0.0", "signal-exit": "^4.1.0", "wrap-ansi": "^6.2.0", - "yoctocolors-cjs": "^2.1.2" + "yoctocolors-cjs": "^2.1.3" }, "engines": { "node": ">=18" @@ -3885,15 +4731,15 @@ } }, "node_modules/@inquirer/editor": { - "version": "4.2.21", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.21.tgz", - "integrity": "sha512-MjtjOGjr0Kh4BciaFShYpZ1s9400idOdvQ5D7u7lE6VztPFoyLcVNE5dXBmEEIQq5zi4B9h2kU+q7AVBxJMAkQ==", + "version": "4.2.23", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.3.0", - "@inquirer/external-editor": "^1.0.2", - "@inquirer/type": "^3.0.9" + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" }, "engines": { "node": ">=18" @@ -3908,15 +4754,15 @@ } }, "node_modules/@inquirer/expand": { - "version": "4.0.21", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.21.tgz", - "integrity": "sha512-+mScLhIcbPFmuvU3tAGBed78XvYHSvCl6dBiYMlzCLhpr0bzGzd8tfivMMeqND6XZiaZ1tgusbUHJEfc6YzOdA==", + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", + "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.3.0", - "@inquirer/type": "^3.0.9", - "yoctocolors-cjs": "^2.1.2" + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" }, "engines": { "node": ">=18" @@ -3931,13 +4777,13 @@ } }, "node_modules/@inquirer/external-editor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.2.tgz", - "integrity": "sha512-yy9cOoBnx58TlsPrIxauKIFQTiyH+0MK4e97y4sV9ERbI+zDxw7i2hxHLCIEGIE/8PPvDxGhgzIOTSOWcs6/MQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", "dev": true, "license": "MIT", "dependencies": { - "chardet": "^2.1.0", + "chardet": "^2.1.1", "iconv-lite": "^0.7.0" }, "engines": { @@ -3953,9 +4799,9 @@ } }, "node_modules/@inquirer/figures": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.14.tgz", - "integrity": "sha512-DbFgdt+9/OZYFM+19dbpXOSeAstPy884FPy1KjDu4anWwymZeOYhMY1mdFri172htv6mvc/uvIAAi7b7tvjJBQ==", + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", "dev": true, "license": "MIT", "engines": { @@ -3963,14 +4809,14 @@ } }, "node_modules/@inquirer/input": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.2.5.tgz", - "integrity": "sha512-7GoWev7P6s7t0oJbenH0eQ0ThNdDJbEAEtVt9vsrYZ9FulIokvd823yLyhQlWHJPGce1wzP53ttfdCZmonMHyA==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", + "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.3.0", - "@inquirer/type": "^3.0.9" + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" }, "engines": { "node": ">=18" @@ -3985,14 +4831,14 @@ } }, "node_modules/@inquirer/number": { - "version": "3.0.21", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.21.tgz", - "integrity": "sha512-5QWs0KGaNMlhbdhOSCFfKsW+/dcAVC2g4wT/z2MCiZM47uLgatC5N20kpkDQf7dHx+XFct/MJvvNGy6aYJn4Pw==", + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", + "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.3.0", - "@inquirer/type": "^3.0.9" + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" }, "engines": { "node": ">=18" @@ -4007,15 +4853,15 @@ } }, "node_modules/@inquirer/password": { - "version": "4.0.21", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.21.tgz", - "integrity": "sha512-xxeW1V5SbNFNig2pLfetsDb0svWlKuhmr7MPJZMYuDnCTkpVBI+X/doudg4pznc1/U+yYmWFFOi4hNvGgUo7EA==", + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", + "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^1.0.1", - "@inquirer/core": "^10.3.0", - "@inquirer/type": "^3.0.9" + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" }, "engines": { "node": ">=18" @@ -4060,15 +4906,15 @@ } }, "node_modules/@inquirer/rawlist": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.9.tgz", - "integrity": "sha512-AWpxB7MuJrRiSfTKGJ7Y68imYt8P9N3Gaa7ySdkFj1iWjr6WfbGAhdZvw/UnhFXTHITJzxGUI9k8IX7akAEBCg==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", + "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.3.0", - "@inquirer/type": "^3.0.9", - "yoctocolors-cjs": "^2.1.2" + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" }, "engines": { "node": ">=18" @@ -4083,16 +4929,16 @@ } }, "node_modules/@inquirer/search": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.0.tgz", - "integrity": "sha512-a5SzB/qrXafDX1Z4AZW3CsVoiNxcIYCzYP7r9RzrfMpaLpB+yWi5U8BWagZyLmwR0pKbbL5umnGRd0RzGVI8bQ==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", + "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.3.0", - "@inquirer/figures": "^1.0.14", - "@inquirer/type": "^3.0.9", - "yoctocolors-cjs": "^2.1.2" + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" }, "engines": { "node": ">=18" @@ -4107,17 +4953,17 @@ } }, "node_modules/@inquirer/select": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.0.tgz", - "integrity": "sha512-kaC3FHsJZvVyIjYBs5Ih8y8Bj4P/QItQWrZW22WJax7zTN+ZPXVGuOM55vzbdCP9zKUiBd9iEJVdesujfF+cAA==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", + "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/ansi": "^1.0.1", - "@inquirer/core": "^10.3.0", - "@inquirer/figures": "^1.0.14", - "@inquirer/type": "^3.0.9", - "yoctocolors-cjs": "^2.1.2" + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" }, "engines": { "node": ">=18" @@ -4132,9 +4978,9 @@ } }, "node_modules/@inquirer/type": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.9.tgz", - "integrity": "sha512-QPaNt/nmE2bLGQa9b7wwyRJoLZ7pN6rcyXvzU0YCmivmJyq1BVo94G98tStRWkoD1RgDX5C+dPlhhHzNdu/W/w==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", "dev": true, "license": "MIT", "engines": { @@ -4870,15 +5716,39 @@ ] }, "node_modules/@mermaid-js/parser": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.0.0.tgz", - "integrity": "sha512-vvK0Hi/VWndxoh03Mmz6wa1KDriSPjS2XMZL/1l19HFwygiObEEoEwSDxOqyLzzAI6J2PU3261JjTMTO7x+BPw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.0.1.tgz", + "integrity": "sha512-opmV19kN1JsK0T6HhhokHpcVkqKpF+x2pPDKKM2ThHtZAB5F4PROopk0amuVYK5qMrIA4erzpNm8gmPNJgMDxQ==", "license": "MIT", "optional": true, "dependencies": { "langium": "^4.0.0" } }, + "node_modules/@mitre-attack/attack-data-model": { + "version": "5.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@mitre-attack/attack-data-model/-/attack-data-model-5.0.0-beta.3.tgz", + "integrity": "sha512-/TBIrix6p51p9Bu9JHUmpPyN8lPnDgOd0UxIGmZdC4zzONpx5wpAvofACQV2t2A9P3EeaYxTcnufTMCh94DZIQ==", + "license": "APACHE-2.0", + "dependencies": { + "axios": "^1.9.0", + "uuid": "^10.0.0", + "zod": "^4.0.5" + } + }, + "node_modules/@mitre-attack/attack-data-model/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", @@ -5450,13 +6320,13 @@ } }, "node_modules/@npmcli/git/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@npmcli/git/node_modules/lru-cache": { @@ -5528,27 +6398,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/@npmcli/package-json/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@npmcli/promise-spawn": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-8.0.3.tgz", @@ -5563,13 +6412,13 @@ } }, "node_modules/@npmcli/promise-spawn/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@npmcli/promise-spawn/node_modules/which": { @@ -5617,13 +6466,13 @@ } }, "node_modules/@npmcli/run-script/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@npmcli/run-script/node_modules/which": { @@ -5672,9 +6521,9 @@ } }, "node_modules/@octokit/endpoint": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.2.tgz", - "integrity": "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.3.tgz", + "integrity": "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==", "dev": true, "license": "MIT", "dependencies": { @@ -5724,9 +6573,9 @@ } }, "node_modules/@octokit/plugin-retry": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-8.0.3.tgz", - "integrity": "sha512-vKGx1i3MC0za53IzYBSBXcrhmd+daQDzuZfYDd52X5S0M2otf3kVZTVP8bLA3EkU0lTvd1WEC2OlNNa4G+dohA==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-8.1.0.tgz", + "integrity": "sha512-O1FZgXeiGb2sowEr/hYTr6YunGdSAFWnr2fyW39Ah85H8O33ELASQxcvOFF5LE6Tjekcyu2ms4qAzJVhSaJxTw==", "dev": true, "license": "MIT", "dependencies": { @@ -5759,16 +6608,17 @@ } }, "node_modules/@octokit/request": { - "version": "10.0.7", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.7.tgz", - "integrity": "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA==", + "version": "10.0.8", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.8.tgz", + "integrity": "sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/endpoint": "^11.0.2", + "@octokit/endpoint": "^11.0.3", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", + "json-with-bigint": "^3.5.3", "universal-user-agent": "^7.0.2" }, "engines": { @@ -5799,18 +6649,18 @@ } }, "node_modules/@parcel/watcher": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", - "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, "dependencies": { - "detect-libc": "^1.0.3", + "detect-libc": "^2.0.3", "is-glob": "^4.0.3", - "micromatch": "^4.0.5", - "node-addon-api": "^7.0.0" + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">= 10.0.0" @@ -5820,25 +6670,25 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.1", - "@parcel/watcher-darwin-arm64": "2.5.1", - "@parcel/watcher-darwin-x64": "2.5.1", - "@parcel/watcher-freebsd-x64": "2.5.1", - "@parcel/watcher-linux-arm-glibc": "2.5.1", - "@parcel/watcher-linux-arm-musl": "2.5.1", - "@parcel/watcher-linux-arm64-glibc": "2.5.1", - "@parcel/watcher-linux-arm64-musl": "2.5.1", - "@parcel/watcher-linux-x64-glibc": "2.5.1", - "@parcel/watcher-linux-x64-musl": "2.5.1", - "@parcel/watcher-win32-arm64": "2.5.1", - "@parcel/watcher-win32-ia32": "2.5.1", - "@parcel/watcher-win32-x64": "2.5.1" + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" } }, "node_modules/@parcel/watcher-android-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", - "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", "cpu": [ "arm64" ], @@ -5857,9 +6707,9 @@ } }, "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", - "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", "cpu": [ "arm64" ], @@ -5878,9 +6728,9 @@ } }, "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", - "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", "cpu": [ "x64" ], @@ -5899,9 +6749,9 @@ } }, "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", - "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", "cpu": [ "x64" ], @@ -5920,9 +6770,9 @@ } }, "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", - "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", "cpu": [ "arm" ], @@ -5941,9 +6791,9 @@ } }, "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", - "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", "cpu": [ "arm" ], @@ -5962,9 +6812,9 @@ } }, "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", - "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", "cpu": [ "arm64" ], @@ -5983,9 +6833,9 @@ } }, "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", - "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", "cpu": [ "arm64" ], @@ -6004,9 +6854,9 @@ } }, "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", - "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", "cpu": [ "x64" ], @@ -6025,9 +6875,9 @@ } }, "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", - "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", "cpu": [ "x64" ], @@ -6046,9 +6896,9 @@ } }, "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", - "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", "cpu": [ "arm64" ], @@ -6067,9 +6917,9 @@ } }, "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", - "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", "cpu": [ "ia32" ], @@ -6088,9 +6938,9 @@ } }, "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", - "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", "cpu": [ "x64" ], @@ -6108,20 +6958,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/@parcel/watcher/node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "bin": { - "detect-libc": "bin/detect-libc.js" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/@parcel/watcher/node_modules/node-addon-api": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", @@ -6130,6 +6966,20 @@ "license": "MIT", "optional": true }, + "node_modules/@parcel/watcher/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -6199,6 +7049,13 @@ "node": ">=12" } }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.59.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", @@ -6566,6 +7423,54 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@schematics/angular/node_modules/@angular-devkit/core": { + "version": "19.2.22", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.22.tgz", + "integrity": "sha512-OqN/Ded+ZKypPZN5+qUFwtnKGl7FKpxJXYO2Vts5vLBojY5goCZd9SGW1CyXeuPnisRUW+vjqBQbWYuEUh36Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.18.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.2", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^4.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@schematics/angular/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@schematics/angular/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, "node_modules/@sec-ant/readable-stream": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", @@ -6597,9 +7502,9 @@ } }, "node_modules/@semantic-release/commit-analyzer/node_modules/conventional-changelog-angular": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.1.0.tgz", - "integrity": "sha512-GGf2Nipn1RUCAktxuVauVr1e3r8QrLP/B0lEUsFktmGqc3ddbQkhoJZHJctVU829U1c6mTSWftrVOCHaL85Q3w==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.3.0.tgz", + "integrity": "sha512-DOuBwYSqWzfwuRByY9O4oOIvDlkUCTDzfbOgcSbkY+imXXj+4tmrEFao3K+FxemClYfYnZzsvudbwrhje9VHDA==", "dev": true, "license": "ISC", "dependencies": { @@ -6609,35 +7514,6 @@ "node": ">=18" } }, - "node_modules/@semantic-release/commit-analyzer/node_modules/conventional-commits-parser": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.2.1.tgz", - "integrity": "sha512-20pyHgnO40rvfI0NGF/xiEoFMkXDtkF8FwHvk5BokoFoCuTQRI8vrNCNFWUOfuolKJMm1tPCHc8GgYEtr1XRNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "meow": "^13.0.0" - }, - "bin": { - "conventional-commits-parser": "dist/cli/index.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@semantic-release/commit-analyzer/node_modules/meow": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", - "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@semantic-release/error": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-3.0.0.tgz", @@ -6649,9 +7525,9 @@ } }, "node_modules/@semantic-release/github": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-12.0.2.tgz", - "integrity": "sha512-qyqLS+aSGH1SfXIooBKjs7mvrv0deg8v+jemegfJg1kq6ji+GJV8CO08VJDEsvjp3O8XJmTTIAjjZbMzagzsdw==", + "version": "12.0.6", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-12.0.6.tgz", + "integrity": "sha512-aYYFkwHW3c6YtHwQF0t0+lAjlU+87NFOZuH2CvWFD0Ylivc7MwhZMiHOJ0FMpIgPpCVib/VUAcOwvrW0KnxQtA==", "dev": true, "license": "MIT", "dependencies": { @@ -6707,9 +7583,9 @@ } }, "node_modules/@semantic-release/github/node_modules/undici": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.18.2.tgz", - "integrity": "sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==", + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz", + "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==", "dev": true, "license": "MIT", "engines": { @@ -6717,13 +7593,13 @@ } }, "node_modules/@semantic-release/npm": { - "version": "13.1.3", - "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-13.1.3.tgz", - "integrity": "sha512-q7zreY8n9V0FIP1Cbu63D+lXtRAVAIWb30MH5U3TdrfXt6r2MIrWCY0whAImN53qNvSGp0Zt07U95K+Qp9GpEg==", + "version": "13.1.5", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-13.1.5.tgz", + "integrity": "sha512-Hq5UxzoatN3LHiq2rTsWS54nCdqJHlsssGERCo8WlvdfFA9LoN0vO+OuKVSjtNapIc/S8C2LBj206wKLHg62mg==", "dev": true, "license": "MIT", "dependencies": { - "@actions/core": "^2.0.0", + "@actions/core": "^3.0.0", "@semantic-release/error": "^4.0.0", "aggregate-error": "^5.0.0", "env-ci": "^11.2.0", @@ -6731,7 +7607,7 @@ "fs-extra": "^11.0.0", "lodash-es": "^4.17.21", "nerf-dart": "^1.0.0", - "normalize-url": "^8.0.0", + "normalize-url": "^9.0.0", "npm": "^11.6.2", "rc": "^1.2.8", "read-pkg": "^10.0.0", @@ -6747,41 +7623,41 @@ } }, "node_modules/@semantic-release/npm/node_modules/@actions/core": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@actions/core/-/core-2.0.2.tgz", - "integrity": "sha512-Ast1V7yHbGAhplAsuVlnb/5J8Mtr/Zl6byPPL+Qjq3lmfIgWF1ak1iYfF/079cRERiuTALTXkSuEUdZeDCfGtA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-3.0.0.tgz", + "integrity": "sha512-zYt6cz+ivnTmiT/ksRVriMBOiuoUpDCJJlZ5KPl2/FRdvwU3f7MPh9qftvbkXJThragzUZieit2nyHUyw53Seg==", "dev": true, "license": "MIT", "dependencies": { - "@actions/exec": "^2.0.0", - "@actions/http-client": "^3.0.1" + "@actions/exec": "^3.0.0", + "@actions/http-client": "^4.0.0" } }, "node_modules/@semantic-release/npm/node_modules/@actions/exec": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-2.0.0.tgz", - "integrity": "sha512-k8ngrX2voJ/RIN6r9xB82NVqKpnMRtxDoiO+g3olkIUpQNqjArXrCQceduQZCQj3P3xm32pChRLqRrtXTlqhIw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-3.0.0.tgz", + "integrity": "sha512-6xH/puSoNBXb72VPlZVm7vQ+svQpFyA96qdDBvhB8eNZOE8LtPf9L4oAsfzK/crCL8YZ+19fKYVnM63Sl+Xzlw==", "dev": true, "license": "MIT", "dependencies": { - "@actions/io": "^2.0.0" + "@actions/io": "^3.0.2" } }, "node_modules/@semantic-release/npm/node_modules/@actions/http-client": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-3.0.1.tgz", - "integrity": "sha512-SbGS8c/vySbNO3kjFgSW77n83C4MQx/Yoe+b1hAdpuvfHxnkHzDq2pWljUpAA56Si1Gae/7zjeZsV0CYjmLo/w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-4.0.0.tgz", + "integrity": "sha512-QuwPsgVMsD6qaPD57GLZi9sqzAZCtiJT8kVBCDpLtxhL5MydQ4gS+DrejtZZPdIYyB1e95uCK9Luyds7ybHI3g==", "dev": true, "license": "MIT", "dependencies": { "tunnel": "^0.0.6", - "undici": "^5.28.5" + "undici": "^6.23.0" } }, "node_modules/@semantic-release/npm/node_modules/@actions/io": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@actions/io/-/io-2.0.0.tgz", - "integrity": "sha512-Jv33IN09XLO+0HS79aaODsvIRyduiF7NY/F6LYeK5oeUmrsz7aFdRphQjFoESF4jS7lMauDOttKALcpapVDIAg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-3.0.2.tgz", + "integrity": "sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==", "dev": true, "license": "MIT" }, @@ -6852,19 +7728,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/hosted-git-info": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", - "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^11.1.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/@semantic-release/npm/node_modules/human-signals": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", @@ -6888,31 +7751,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/lru-cache": { - "version": "11.2.4", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", - "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@semantic-release/npm/node_modules/normalize-package-data": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-8.0.0.tgz", - "integrity": "sha512-RWk+PI433eESQ7ounYxIp67CYuVsS1uYSonX3kA6ps/3LWfjVQa/ptEg6Y3T6uAMq1mWpX9PQ+qx+QaHpsc7gQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^9.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/@semantic-release/npm/node_modules/npm-run-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", @@ -6930,24 +7768,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/parse-json": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", - "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.26.2", - "index-to-position": "^1.1.0", - "type-fest": "^4.39.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@semantic-release/npm/node_modules/path-key": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", @@ -6961,42 +7781,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/npm/node_modules/read-pkg": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-10.0.0.tgz", - "integrity": "sha512-A70UlgfNdKI5NSvTTfHzLQj7NJRpJ4mT5tGafkllJ4wh71oYuGm/pzphHcmW4s35iox56KSK721AihodoXSc/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/normalize-package-data": "^2.4.4", - "normalize-package-data": "^8.0.0", - "parse-json": "^8.3.0", - "type-fest": "^5.2.0", - "unicorn-magic": "^0.3.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@semantic-release/npm/node_modules/read-pkg/node_modules/type-fest": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.4.1.tgz", - "integrity": "sha512-xygQcmneDyzsEuKZrFbRMne5HDqMs++aFzefrJTgEIKjQ3rekM+RPfFCVq2Gp1VIDqddoYeppCj4Pcb+RZW0GQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "dependencies": { - "tagged-tag": "^1.0.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@semantic-release/npm/node_modules/strip-final-newline": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", @@ -7010,6 +7794,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@semantic-release/npm/node_modules/undici": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", + "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, "node_modules/@semantic-release/npm/node_modules/unicorn-magic": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", @@ -7049,9 +7843,9 @@ } }, "node_modules/@semantic-release/release-notes-generator/node_modules/conventional-changelog-angular": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.1.0.tgz", - "integrity": "sha512-GGf2Nipn1RUCAktxuVauVr1e3r8QrLP/B0lEUsFktmGqc3ddbQkhoJZHJctVU829U1c6mTSWftrVOCHaL85Q3w==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.3.0.tgz", + "integrity": "sha512-DOuBwYSqWzfwuRByY9O4oOIvDlkUCTDzfbOgcSbkY+imXXj+4tmrEFao3K+FxemClYfYnZzsvudbwrhje9VHDA==", "dev": true, "license": "ISC", "dependencies": { @@ -7061,41 +7855,103 @@ "node": ">=18" } }, - "node_modules/@semantic-release/release-notes-generator/node_modules/conventional-commits-parser": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.2.1.tgz", - "integrity": "sha512-20pyHgnO40rvfI0NGF/xiEoFMkXDtkF8FwHvk5BokoFoCuTQRI8vrNCNFWUOfuolKJMm1tPCHc8GgYEtr1XRNA==", + "node_modules/@semantic-release/release-notes-generator/node_modules/get-stream": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-7.0.1.tgz", + "integrity": "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==", "dev": true, "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@semantic-release/release-notes-generator/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "license": "ISC", "dependencies": { - "meow": "^13.0.0" + "lru-cache": "^10.0.1" }, - "bin": { - "conventional-commits-parser": "dist/cli/index.js" + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@semantic-release/release-notes-generator/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@semantic-release/release-notes-generator/node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@semantic-release/release-notes-generator/node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" }, "engines": { "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/release-notes-generator/node_modules/get-stream": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-7.0.1.tgz", - "integrity": "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==", + "node_modules/@semantic-release/release-notes-generator/node_modules/read-package-up": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", + "integrity": "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==", "dev": true, "license": "MIT", + "dependencies": { + "find-up-simple": "^1.0.0", + "read-pkg": "^9.0.0", + "type-fest": "^4.6.0" + }, "engines": { - "node": ">=16" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@semantic-release/release-notes-generator/node_modules/meow": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", - "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "node_modules/@semantic-release/release-notes-generator/node_modules/read-pkg": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", "dev": true, "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" + }, "engines": { "node": ">=18" }, @@ -7103,6 +7959,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@semantic-release/release-notes-generator/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@sigstore/bundle": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-3.1.0.tgz", @@ -7197,9 +8066,9 @@ } }, "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", "dev": true, "license": "MIT" }, @@ -7229,17 +8098,72 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", - "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "node_modules/@ts-morph/common": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.22.0.tgz", + "integrity": "sha512-HqNBuV/oIlMKdkLshXd1zKBqNQCsuPEsgQOkfFQ/eUKjRlwndXW1AjN9LVkBEIukm00gGXSRmfkl0Wv5VXLnlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.3.2", + "minimatch": "^9.0.3", + "mkdirp": "^3.0.1", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@ts-morph/common/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, "license": "MIT" }, + "node_modules/@ts-morph/common/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@ts-morph/common/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@ts-morph/common/node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", "dev": true, "license": "MIT" }, @@ -7288,6 +8212,39 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/@tufjs/models/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, + "license": "MIT" + }, + "node_modules/@tufjs/models/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@tufjs/models/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@types/body-parser": { "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", @@ -7309,6 +8266,17 @@ "@types/node": "*" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -7340,16 +8308,6 @@ "@types/node": "*" } }, - "node_modules/@types/cors": { - "version": "2.8.19", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", - "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/d3": { "version": "7.4.3", "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", @@ -7583,9 +8541,9 @@ "optional": true }, "node_modules/@types/d3-shape": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", - "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", "license": "MIT", "optional": true, "dependencies": { @@ -7634,6 +8592,13 @@ "@types/d3-selection": "*" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/eslint": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", @@ -7713,23 +8678,6 @@ "@types/node": "*" } }, - "node_modules/@types/jasmine": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-6.0.0.tgz", - "integrity": "sha512-18lgGsLmEh3VJk9eZ5wAjTISxdqzl6YOwu8UdMpolajN57QOCNbl+AbHUd+Yu9ItrsFdB+c8LSZSGNg8nHaguw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/jasminewd2": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@types/jasminewd2/-/jasminewd2-2.0.13.tgz", - "integrity": "sha512-aJ3wj8tXMpBrzQ5ghIaqMisD8C3FIrcO6sDKHqFbuqAsI7yOxj0fA7MrRCPLZHIVUjERIwsMmGn/vB0UQ9u0Hg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/jasmine": "*" - } - }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -7745,9 +8693,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.19.24", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.24.tgz", - "integrity": "sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==", + "version": "20.19.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", + "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -7851,6 +8799,13 @@ "license": "MIT", "optional": true }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/ws": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", @@ -7862,17 +8817,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.1.tgz", - "integrity": "sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.0.tgz", + "integrity": "sha512-qeu4rTHR3/IaFORbD16gmjq9+rEs9fGKdX0kF6BKSfi+gCuG3RCKLlSBYzn/bGsY9Tj7KE/DAQStbp8AHJGHEQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.53.1", - "@typescript-eslint/type-utils": "8.53.1", - "@typescript-eslint/utils": "8.53.1", - "@typescript-eslint/visitor-keys": "8.53.1", + "@typescript-eslint/scope-manager": "8.57.0", + "@typescript-eslint/type-utils": "8.57.0", + "@typescript-eslint/utils": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" @@ -7885,22 +8840,22 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.53.1", - "eslint": "^8.57.0 || ^9.0.0", + "@typescript-eslint/parser": "^8.57.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.1.tgz", - "integrity": "sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.0.tgz", + "integrity": "sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.53.1", - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/typescript-estree": "8.53.1", - "@typescript-eslint/visitor-keys": "8.53.1", + "@typescript-eslint/scope-manager": "8.57.0", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0", "debug": "^4.4.3" }, "engines": { @@ -7911,19 +8866,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.53.1.tgz", - "integrity": "sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.0.tgz", + "integrity": "sha512-pR+dK0BlxCLxtWfaKQWtYr7MhKmzqZxuii+ZjuFlZlIGRZm22HnXFqa2eY+90MUz8/i80YJmzFGDUsi8dMOV5w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.53.1", - "@typescript-eslint/types": "^8.53.1", + "@typescript-eslint/tsconfig-utils": "^8.57.0", + "@typescript-eslint/types": "^8.57.0", "debug": "^4.4.3" }, "engines": { @@ -7938,14 +8893,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz", - "integrity": "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.0.tgz", + "integrity": "sha512-nvExQqAHF01lUM66MskSaZulpPL5pgy5hI5RfrxviLgzZVffB5yYzw27uK/ft8QnKXI2X0LBrHJFr1TaZtAibw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/visitor-keys": "8.53.1" + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7956,9 +8911,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.1.tgz", - "integrity": "sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.0.tgz", + "integrity": "sha512-LtXRihc5ytjJIQEH+xqjB0+YgsV4/tW35XKX3GTZHpWtcC8SPkT/d4tqdf1cKtesryHm2bgp6l555NYcT2NLvA==", "dev": true, "license": "MIT", "engines": { @@ -7973,15 +8928,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.53.1.tgz", - "integrity": "sha512-MOrdtNvyhy0rHyv0ENzub1d4wQYKb2NmIqG7qEqPWFW7Mpy2jzFC3pQ2yKDvirZB7jypm5uGjF2Qqs6OIqu47w==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.0.tgz", + "integrity": "sha512-yjgh7gmDcJ1+TcEg8x3uWQmn8ifvSupnPfjP21twPKrDP/pTHlEQgmKcitzF/rzPSmv7QjJ90vRpN4U+zoUjwQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/typescript-estree": "8.53.1", - "@typescript-eslint/utils": "8.53.1", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0", + "@typescript-eslint/utils": "8.57.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, @@ -7993,14 +8948,14 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.1.tgz", - "integrity": "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.0.tgz", + "integrity": "sha512-dTLI8PEXhjUC7B9Kre+u0XznO696BhXcTlOn0/6kf1fHaQW8+VjJAVHJ3eTI14ZapTxdkOmc80HblPQLaEeJdg==", "dev": true, "license": "MIT", "engines": { @@ -8012,18 +8967,18 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.1.tgz", - "integrity": "sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.0.tgz", + "integrity": "sha512-m7faHcyVg0BT3VdYTlX8GdJEM7COexXxS6KqGopxdtkQRvBanK377QDHr4W/vIPAR+ah9+B/RclSW5ldVniO1Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.53.1", - "@typescript-eslint/tsconfig-utils": "8.53.1", - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/visitor-keys": "8.53.1", + "@typescript-eslint/project-service": "8.57.0", + "@typescript-eslint/tsconfig-utils": "8.57.0", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/visitor-keys": "8.57.0", "debug": "^4.4.3", - "minimatch": "^9.0.5", + "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" @@ -8040,9 +8995,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -8053,16 +9008,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.1.tgz", - "integrity": "sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.0.tgz", + "integrity": "sha512-5iIHvpD3CZe06riAsbNxxreP+MuYgVUsV0n4bwLH//VJmgtt54sQeY2GszntJ4BjYCpMzrfVh2SBnUQTtys2lQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.53.1", - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/typescript-estree": "8.53.1" + "@typescript-eslint/scope-manager": "8.57.0", + "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -8072,19 +9027,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.1.tgz", - "integrity": "sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.0.tgz", + "integrity": "sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.53.1", - "eslint-visitor-keys": "^4.2.1" + "@typescript-eslint/types": "8.57.0", + "eslint-visitor-keys": "^5.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -8095,13 +9050,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" @@ -8114,6 +9069,17 @@ "dev": true, "license": "ISC" }, + "node_modules/@upsetjs/venn.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz", + "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==", + "license": "MIT", + "optional": true, + "optionalDependencies": { + "d3-selection": "^3.0.0", + "d3-transition": "^3.0.1" + } + }, "node_modules/@vitejs/plugin-basic-ssl": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.2.0.tgz", @@ -8127,42 +9093,213 @@ "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" } }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "node_modules/@vitest/coverage-v8": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", + "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", "dev": true, "license": "MIT", "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^1.0.2", + "ast-v8-to-istanbul": "^0.3.3", + "debug": "^4.4.1", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.17", + "magicast": "^0.3.5", + "std-env": "^3.9.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "3.2.4", + "vitest": "3.2.4" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } } }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/ui": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-3.2.4.tgz", + "integrity": "sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "fflate": "^0.8.2", + "flatted": "^3.3.3", + "pathe": "^2.0.3", + "sirv": "^3.0.1", + "tinyglobby": "^0.2.14", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "3.2.4" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dev": true, "license": "MIT", "dependencies": { @@ -8344,9 +9481,9 @@ } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "devOptional": true, "license": "MIT", "bin": { @@ -8380,9 +9517,9 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", "dev": true, "license": "MIT", "dependencies": { @@ -8541,6 +9678,54 @@ "typescript-eslint": "^8.0.0" } }, + "node_modules/angular-eslint/node_modules/@angular-devkit/core": { + "version": "19.2.22", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.22.tgz", + "integrity": "sha512-OqN/Ded+ZKypPZN5+qUFwtnKGl7FKpxJXYO2Vts5vLBojY5goCZd9SGW1CyXeuPnisRUW+vjqBQbWYuEUh36Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.18.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.2", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^4.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/angular-eslint/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/angular-eslint/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -8552,9 +9737,9 @@ } }, "node_modules/ansi-escapes": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.1.1.tgz", - "integrity": "sha512-Zhl0ErHcSRUaVfGUeUdDuLgpkEo8KIFjB4Y9uAc46ScOpdDiU1Dbyplh7qWJeJ/ZHpbyMSM26+X3BySgnIz40Q==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", "dev": true, "license": "MIT", "dependencies": { @@ -8640,16 +9825,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/app-root-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", - "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -8705,12 +9880,40 @@ "node": ">=8" } }, - "node_modules/ast-types-flow": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", + "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, - "license": "ISC" + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.12.tgz", + "integrity": "sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" }, "node_modules/autoprefixer": { "version": "10.4.20", @@ -8750,6 +9953,17 @@ "postcss": "^8.1.0" } }, + "node_modules/axios": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", + "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -8779,14 +9993,14 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", - "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.16.tgz", + "integrity": "sha512-xaVwwSfebXf0ooE11BJovZYKhFjIvQo7TsyVpETuIeH2JHv0k/T6Y5j22pPTvqYqmpkxdlPAJlyJ0tfOJAoMxw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.7", - "@babel/helper-define-polyfill-provider": "^0.6.5", + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.7", "semver": "^6.3.1" }, "peerDependencies": { @@ -8818,24 +10032,27 @@ } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", - "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.7.tgz", + "integrity": "sha512-OTYbUlSwXhNgr4g6efMZgsO8//jA61P7ZbRX3iTT53VON8l+WQS8IAUEVo4a4cWknrg2W8Cj4gQhRYNCJ8GkAA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5" + "@babel/helper-define-polyfill-provider": "^0.6.7" }, "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==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } }, "node_modules/base64-js": { "version": "1.5.1", @@ -8858,16 +10075,6 @@ ], "license": "MIT" }, - "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, - "license": "MIT", - "engines": { - "node": "^4.5.0 || >= 5.9" - } - }, "node_modules/baseline-browser-mapping": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", @@ -8915,6 +10122,16 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -9031,13 +10248,16 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/braces": { @@ -9119,16 +10339,6 @@ "dev": true, "license": "MIT" }, - "node_modules/builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha512-wxXCdllwGhI2kCC0MnvTGYTMvnVZTvqgypkiTI8Pa5tcz2i6VqsqwYGgqwXji+4RgCzms6EajE4IxiUH6HH8nQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/bundle-name": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", @@ -9155,6 +10365,16 @@ "node": ">= 0.8" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/cacache": { "version": "19.0.1", "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", @@ -9189,27 +10409,6 @@ "node": ">=18" } }, - "node_modules/cacache/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/cacache/node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -9218,9 +10417,9 @@ "license": "ISC" }, "node_modules/cacache/node_modules/tar": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", - "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", + "version": "7.5.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.11.tgz", + "integrity": "sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -9248,7 +10447,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -9328,6 +10526,23 @@ "node": ">=0.8" } }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "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": ">=18" + } + }, "node_modules/chalk": { "version": "5.6.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", @@ -9358,18 +10573,28 @@ "dev": true, "license": "MIT" }, - "node_modules/chevrotain": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.1.tgz", - "integrity": "sha512-f0yv5CPKaFxfsPTBzX7vGuim4oIC1/gcS7LUGdBSwl2dU6+FON6LVUksdOo1qJjoUvXNn45urgh8C+0a24pACQ==", - "license": "Apache-2.0", + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chevrotain": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.2.tgz", + "integrity": "sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg==", + "license": "Apache-2.0", "optional": true, "dependencies": { - "@chevrotain/cst-dts-gen": "11.1.1", - "@chevrotain/gast": "11.1.1", - "@chevrotain/regexp-to-ast": "11.1.1", - "@chevrotain/types": "11.1.1", - "@chevrotain/utils": "11.1.1", + "@chevrotain/cst-dts-gen": "11.1.2", + "@chevrotain/gast": "11.1.2", + "@chevrotain/regexp-to-ast": "11.1.2", + "@chevrotain/types": "11.1.2", + "@chevrotain/utils": "11.1.2", "lodash-es": "4.17.23" } }, @@ -9908,118 +11133,10 @@ "node": ">=0.10.0" } }, - "node_modules/codelyzer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-6.0.2.tgz", - "integrity": "sha512-v3+E0Ucu2xWJMOJ2fA/q9pDT/hlxHftHGPUay1/1cTgyPV5JTHFdO9hqo837Sx2s9vKBMTt5gO+lhF95PO6J+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular/compiler": "9.0.0", - "@angular/core": "9.0.0", - "app-root-path": "^3.0.0", - "aria-query": "^3.0.0", - "axobject-query": "2.0.2", - "css-selector-tokenizer": "^0.7.1", - "cssauron": "^1.4.0", - "damerau-levenshtein": "^1.0.4", - "rxjs": "^6.5.3", - "semver-dsl": "^1.0.1", - "source-map": "^0.5.7", - "sprintf-js": "^1.1.2", - "tslib": "^1.10.0", - "zone.js": "~0.10.3" - }, - "peerDependencies": { - "@angular/compiler": ">=2.3.1 <13.0.0 || ^12.0.0-next || ^12.1.0-next || ^12.2.0-next", - "@angular/core": ">=2.3.1 <13.0.0 || ^12.0.0-next || ^12.1.0-next || ^12.2.0-next", - "tslint": "^5.0.0 || ^6.0.0" - } - }, - "node_modules/codelyzer/node_modules/@angular/compiler": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-9.0.0.tgz", - "integrity": "sha512-ctjwuntPfZZT2mNj2NDIVu51t9cvbhl/16epc5xEwyzyDt76pX9UgwvY+MbXrf/C/FWwdtmNtfP698BKI+9leQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "tslib": "^1.10.0" - } - }, - "node_modules/codelyzer/node_modules/@angular/core": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-9.0.0.tgz", - "integrity": "sha512-6Pxgsrf0qF9iFFqmIcWmjJGkkCaCm6V5QNnxMy2KloO3SDq6QuMVRbN9RtC8Urmo25LP+eZ6ZgYqFYpdD8Hd9w==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "rxjs": "^6.5.3", - "tslib": "^1.10.0", - "zone.js": "~0.10.2" - } - }, - "node_modules/codelyzer/node_modules/aria-query": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", - "integrity": "sha512-majUxHgLehQTeSA+hClx+DY09OVUqG3GtezWkF1krgLGNdlDu9l9V8DaqNMWbq4Eddc8wsyDA0hpDUtnYxQEXw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "ast-types-flow": "0.0.7", - "commander": "^2.11.0" - } - }, - "node_modules/codelyzer/node_modules/axobject-query": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz", - "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "ast-types-flow": "0.0.7" - } - }, - "node_modules/codelyzer/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, - "license": "MIT" - }, - "node_modules/codelyzer/node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/codelyzer/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/codelyzer/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true, - "license": "0BSD" - }, - "node_modules/codelyzer/node_modules/zone.js": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.10.3.tgz", - "integrity": "sha512-LXVLVEq0NNOqK/fLJo3d0kfzd4sxwn2/h67/02pjCjfKDxgx1i9QqpvtHD8CrBnSSwMw5+dy11O7FRX5mkO7Cg==", + "node_modules/code-block-writer": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz", + "integrity": "sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==", "dev": true, "license": "MIT" }, @@ -10059,14 +11176,16 @@ "dev": true, "license": "MIT" }, - "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, + "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==", "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, "engines": { - "node": ">=0.1.90" + "node": ">= 0.8" } }, "node_modules/commander": { @@ -10096,6 +11215,20 @@ "node": ">=v18" } }, + "node_modules/commitlint/node_modules/@commitlint/types": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.8.1.tgz", + "integrity": "sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/conventional-commits-parser": "^5.0.0", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" + } + }, "node_modules/common-path-prefix": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", @@ -10191,9 +11324,9 @@ "license": "MIT" }, "node_modules/confbox": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", - "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", "license": "MIT", "optional": true }, @@ -10215,22 +11348,6 @@ "dev": true, "license": "ISC" }, - "node_modules/connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "dev": true, - "license": "MIT", - "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-history-api-fallback": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", @@ -10241,23 +11358,6 @@ "node": ">=0.8" } }, - "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, - "license": "MIT", - "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, - "license": "MIT" - }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -10308,12 +11408,13 @@ } }, "node_modules/conventional-changelog-writer": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.2.0.tgz", - "integrity": "sha512-Y2aW4596l9AEvFJRwFGJGiQjt2sBYTjPD18DdvxX9Vpz0Z7HQ+g1Z+6iYDAm1vR3QOJrDBkRHixHK/+FhkR6Pw==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.4.0.tgz", + "integrity": "sha512-HHBFkk1EECxxmCi4CTu091iuDpQv5/OavuCUAuZmrkWpmYfyD816nom1CvtfXJ/uYfAAjavgHvXHX291tSLK8g==", "dev": true, "license": "MIT", "dependencies": { + "@simple-libs/stream-utils": "^1.2.0", "conventional-commits-filter": "^5.0.0", "handlebars": "^4.7.7", "meow": "^13.0.0", @@ -10326,19 +11427,6 @@ "node": ">=18" } }, - "node_modules/conventional-changelog-writer/node_modules/meow": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", - "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/conventional-commits-filter": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz", @@ -10350,22 +11438,20 @@ } }, "node_modules/conventional-commits-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", - "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.3.0.tgz", + "integrity": "sha512-RfOq/Cqy9xV9bOA8N+ZH6DlrDR+5S3Mi0B5kACEjESpE+AviIpAptx9a9cFpWCCvgRtWT+0BbUw+e1BZfts9jg==", "dev": true, "license": "MIT", "dependencies": { - "is-text-path": "^2.0.0", - "JSONStream": "^1.3.5", - "meow": "^12.0.1", - "split2": "^4.0.0" + "@simple-libs/stream-utils": "^1.2.0", + "meow": "^13.0.0" }, "bin": { - "conventional-commits-parser": "cli.mjs" + "conventional-commits-parser": "dist/cli/index.js" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/convert-hrtime": { @@ -10444,13 +11530,13 @@ } }, "node_modules/core-js-compat": { - "version": "3.46.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.46.0.tgz", - "integrity": "sha512-p9hObIIEENxSV8xIu+V68JjSeARg6UVMG5mR+JEUguG3sI6MsiS1njz2jHmyJDvA+8jX/sytkBHup6kxhM9law==", + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.48.0.tgz", + "integrity": "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.26.3" + "browserslist": "^4.28.1" }, "funding": { "type": "opencollective", @@ -10464,20 +11550,6 @@ "dev": true, "license": "MIT" }, - "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, - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/cose-base": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", @@ -10489,9 +11561,9 @@ } }, "node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", + "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10649,15 +11721,18 @@ "url": "https://github.com/sponsors/fb55" } }, - "node_modules/css-selector-tokenizer": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz", - "integrity": "sha512-jWQv3oCEL5kMErj4wRnK/OPoBi0D+P1FR2cDCKYPaMeD2eW3/mttav8HT4hT1CKopiJI/psEULjkClhvJo4Lvg==", + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", "dev": true, "license": "MIT", "dependencies": { - "cssesc": "^3.0.0", - "fastparse": "^1.1.2" + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, "node_modules/css-what": { @@ -10673,16 +11748,6 @@ "url": "https://github.com/sponsors/fb55" } }, - "node_modules/cssauron": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz", - "integrity": "sha512-Ht70DcFBh+/ekjVrYS2PlDMdSQEl3OFNmjK6lcn49HptBgilXf/Zwg4uFh9Xn0pX3Q8YOkSjIFOfK2osvdqpBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "through": "X.X.X" - } - }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -10696,12 +11761,31 @@ "node": ">=4" } }, - "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==", + "node_modules/cssstyle": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.7.tgz", + "integrity": "sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^4.1.1", + "@csstools/css-syntax-patches-for-csstree": "^1.0.21", + "css-tree": "^3.1.0", + "lru-cache": "^11.2.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cssstyle/node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } }, "node_modules/cytoscape": { "version": "3.33.1", @@ -10999,9 +12083,9 @@ } }, "node_modules/d3-format": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", - "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", "license": "ISC", "optional": true, "engines": { @@ -11257,9 +12341,9 @@ } }, "node_modules/dagre-d3-es": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.13.tgz", - "integrity": "sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==", + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz", + "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==", "license": "MIT", "optional": true, "dependencies": { @@ -11267,13 +12351,6 @@ "lodash-es": "^4.17.21" } }, - "node_modules/damerau-levenshtein": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", - "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", - "dev": true, - "license": "BSD-2-Clause" - }, "node_modules/dargs": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz", @@ -11287,14 +12364,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/data-urls": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.1.tgz", + "integrity": "sha512-euIQENZg6x8mj3fO6o9+fOW8MimUI4PpD/fZBhJfeioZVy9TUpM4UY7KjQNVZFlqwJ0UdzRDzkycB997HEq1BQ==", "dev": true, "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^15.1.0" + }, "engines": { - "node": ">=4.0" + "node": ">=20" + } + }, + "node_modules/data-urls/node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" } }, "node_modules/dayjs": { @@ -11308,7 +12399,7 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -11322,6 +12413,23 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -11405,6 +12513,15 @@ "robust-predicates": "^3.0.2" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/delegate": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", @@ -11451,17 +12568,10 @@ "dev": true, "license": "MIT" }, - "node_modules/di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", - "dev": true, - "license": "MIT" - }, "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -11530,19 +12640,6 @@ "node": ">=6.0.0" } }, - "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, - "license": "MIT", - "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", @@ -11588,14 +12685,11 @@ } }, "node_modules/dompurify": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.2.tgz", - "integrity": "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz", + "integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==", "license": "(MPL-2.0 OR Apache-2.0)", "optional": true, - "engines": { - "node": ">=20" - }, "optionalDependencies": { "@types/trusted-types": "^2.0.7" } @@ -11638,7 +12732,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -11745,9 +12838,9 @@ } }, "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==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true, "license": "MIT", "engines": { @@ -11789,96 +12882,31 @@ "once": "^1.4.0" } }, - "node_modules/engine.io": { - "version": "6.6.4", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", - "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "node_modules/enhanced-resolve": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", + "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.7.2", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.17.1" + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" }, "engines": { - "node": ">=10.2.0" + "node": ">=10.13.0" } }, - "node_modules/engine.io-parser": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", - "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "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, - "license": "MIT", + "license": "BSD-2-Clause", "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/engine.io/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/enhanced-resolve": { - "version": "5.20.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", - "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/ent": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.2.tgz", - "integrity": "sha512-kKvD1tO6BM+oK9HzCPpUdRb4vKFQY/FPTFmurMvh6LlN68VMrdj77w8yp51/kDbpkFOS9J8w5W6zIzgM2H8/hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "punycode": "^1.4.1", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "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, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/env-ci": { @@ -12084,7 +13112,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -12094,16 +13121,15 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" } }, "node_modules/es-module-lexer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", - "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", "dev": true, "license": "MIT" }, @@ -12111,7 +13137,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -12120,6 +13145,21 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.25.4", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", @@ -12280,25 +13320,25 @@ } }, "node_modules/eslint": { - "version": "9.39.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.0.tgz", - "integrity": "sha512-iy2GE3MHrYTL5lrCtMZ0X1KLEKKUjmK0kzwcnefhR66txcEmXZD2YWgR5GNdcEwkNx3a0siYkSvl0vIC+Svjmg==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", + "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "ajv": "^6.12.4", + "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", @@ -12317,7 +13357,7 @@ "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -12387,14 +13427,14 @@ } }, "node_modules/eslint-plugin-unused-imports": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.3.0.tgz", - "integrity": "sha512-ZFBmXMGBYfHttdRtOG9nFFpmUvMtbHSjsKrS20vdWdbfiVYsO3yA2SGYy9i9XmZJDfMGBflZGBCm70SEnFQtOA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.4.1.tgz", + "integrity": "sha512-oZGYUz1X3sRMGUB+0cZyK2VcvRX5lm/vB56PgNNcU+7ficUCKm66oZWKUubXWnOuPjQ8PvmXtCViXBMONPe7tQ==", "dev": true, "license": "MIT", "peerDependencies": { "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0", - "eslint": "^9.0.0 || ^8.0.0" + "eslint": "^10.0.0 || ^9.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "@typescript-eslint/eslint-plugin": { @@ -12433,9 +13473,9 @@ } }, "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -12465,6 +13505,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/eslint/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, + "license": "MIT" + }, "node_modules/eslint/node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -12557,9 +13604,9 @@ } }, "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -12655,24 +13702,10 @@ "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, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -12705,6 +13738,16 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -12773,6 +13816,16 @@ "dev": true, "license": "ISC" }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/exponential-backoff": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", @@ -12837,35 +13890,6 @@ "ms": "2.0.0" } }, - "node_modules/express/node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/finalhandler": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", - "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "statuses": "~2.0.2", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -12873,30 +13897,6 @@ "dev": true, "license": "MIT" }, - "node_modules/express/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/exsolve": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", - "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", - "license": "MIT", - "optional": true - }, - "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, - "license": "MIT" - }, "node_modules/fast-content-type-parse": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", @@ -12989,17 +13989,10 @@ ], "license": "BSD-3-Clause" }, - "node_modules/fastparse": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", - "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", - "dev": true, - "license": "MIT" - }, "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "dev": true, "license": "ISC", "dependencies": { @@ -13037,6 +14030,13 @@ } } }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "dev": true, + "license": "MIT" + }, "node_modules/figures": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", @@ -13093,18 +14093,18 @@ } }, "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", "dev": true, "license": "MIT", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "~2.0.2", "unpipe": "~1.0.0" }, "engines": { @@ -13128,19 +14128,6 @@ "dev": true, "license": "MIT" }, - "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, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/find-cache-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", @@ -13231,9 +14218,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", + "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", "dev": true, "license": "ISC" }, @@ -13241,7 +14228,6 @@ "version": "1.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", - "dev": true, "funding": [ { "type": "individual", @@ -13275,6 +14261,22 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -13363,9 +14365,9 @@ } }, "node_modules/fs-extra": { - "version": "11.3.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", - "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", "dev": true, "license": "MIT", "dependencies": { @@ -13416,7 +14418,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -13456,9 +14457,9 @@ } }, "node_modules/get-east-asian-width": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", - "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", "dev": true, "license": "MIT", "engines": { @@ -13472,7 +14473,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -13497,7 +14497,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -13552,6 +14551,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz", "integrity": "sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==", + "deprecated": "This package is no longer maintained. For the JavaScript API, please use @conventional-changelog/git-client instead.", "dev": true, "license": "MIT", "dependencies": { @@ -13566,23 +14566,36 @@ "node": ">=16" } }, + "node_modules/git-raw-commits/node_modules/meow": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", + "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "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" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": "*" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -13625,28 +14638,37 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/glob/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, + "license": "MIT" + }, "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.2" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/global-directory": { @@ -13736,7 +14758,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -13842,7 +14863,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -13855,7 +14875,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -13871,7 +14890,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -13969,6 +14987,19 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -14044,20 +15075,10 @@ "url": "https://opencollective.com/express" } }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-parser-js": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", - "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", "dev": true, "license": "MIT" }, @@ -14159,9 +15180,9 @@ } }, "node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "dev": true, "license": "MIT", "dependencies": { @@ -14232,6 +15253,39 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/ignore-walk/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, + "license": "MIT" + }, + "node_modules/ignore-walk/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/ignore-walk/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/image-size": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", @@ -14395,9 +15449,9 @@ } }, "node_modules/ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", "dev": true, "license": "MIT", "engines": { @@ -14597,24 +15651,12 @@ "node": ">=0.10.0" } }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "license": "MIT" }, "node_modules/is-stream": { "version": "2.0.1", @@ -14685,19 +15727,6 @@ "dev": true, "license": "MIT" }, - "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, - "license": "MIT", - "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", @@ -14775,88 +15804,18 @@ } }, "node_modules/istanbul-lib-source-maps": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", - "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, "license": "BSD-3-Clause", "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "rimraf": "^2.6.3", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=6" - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/istanbul-lib-source-maps/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, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" + "istanbul-lib-coverage": "^3.0.0" }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/istanbul-lib-source-maps/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, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/istanbul-lib-source-maps/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, - "license": "BSD-3-Clause", "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, "node_modules/istanbul-reports": { @@ -14889,23 +15848,6 @@ "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/jasmine-core": { - "version": "5.12.1", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.12.1.tgz", - "integrity": "sha512-P/UbRZ0LKwXe7wEpwDheuhunPwITn4oPALhrJEQJo6756EwNGnsK/TSQrWojBB4cQDQ+VaxWYws9tFNDuiMh2Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/jasmine-spec-reporter": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-7.0.0.tgz", - "integrity": "sha512-OtC7JRasiTcjsaCBPtMO0Tl8glCejM4J4/dNuOJdA8lBjz4PmWjYQ6pzb0uzpBNAWJMDudYuj9OdXJWqM2QTJg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "colors": "1.4.0" - } - }, "node_modules/java-properties": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz", @@ -14992,6 +15934,72 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.4.0.tgz", + "integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@acemir/cssom": "^0.9.28", + "@asamuzakjp/dom-selector": "^6.7.6", + "@exodus/bytes": "^1.6.0", + "cssstyle": "^5.3.4", + "data-urls": "^6.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^8.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.0", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.1.0", + "ws": "^8.18.3", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/jsdom/node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -15043,6 +16051,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-with-bigint": { + "version": "3.5.7", + "resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.7.tgz", + "integrity": "sha512-7ei3MdAI5+fJPVnKlW77TKNKwQ5ppSzWvhPuSuINT/GYW9ZOC1eRKOuhV9yHG5aEsUPj9BBx5JIekkmoLHxZOw==", + "dev": true, + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -15103,144 +16118,6 @@ "node": "*" } }, - "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, - "license": "MIT", - "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-chrome-launcher": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz", - "integrity": "sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "which": "^1.2.1" - } - }, - "node_modules/karma-chrome-launcher/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/karma-coverage-istanbul-reporter": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-3.0.3.tgz", - "integrity": "sha512-wE4VFhG/QZv2Y4CdAYWDbMmcAHeS926ZIji4z+FkB2aF/EposRb6DP6G5ncT/wXhqUfAb/d7kZrNKPonbvsATw==", - "dev": true, - "license": "MIT", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^3.0.6", - "istanbul-reports": "^3.0.2", - "minimatch": "^3.0.4" - }, - "funding": { - "url": "https://github.com/sponsors/mattlewis92" - } - }, - "node_modules/karma-coverage-istanbul-reporter/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/karma-coverage-istanbul-reporter/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, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/karma-jasmine": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-5.1.0.tgz", - "integrity": "sha512-i/zQLFrfEpRyQoJF9fsCdTMOF5c2dK7C7OmsuKg2D0YSsuZSfQDiLuaiktbuio6F2wiCsZSnSnieIQ0ant/uzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "jasmine-core": "^4.1.0" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "karma": "^6.0.0" - } - }, - "node_modules/karma-jasmine-html-reporter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-2.1.0.tgz", - "integrity": "sha512-sPQE1+nlsn6Hwb5t+HHwyy0A1FNCVKuL1192b+XNauMYWThz2kweiBVW1DqloRpVvZIJkIoHVB7XRpK78n1xbQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "jasmine-core": "^4.0.0 || ^5.0.0", - "karma": "^6.0.0", - "karma-jasmine": "^5.0.0" - } - }, - "node_modules/karma-jasmine/node_modules/jasmine-core": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.6.1.tgz", - "integrity": "sha512-VYz/BjjmC3klLJlLwA4Kw8ytk0zDSmbbDLNs794VnWmkcCB7I9aAL/D48VNQtmITyPvea2C3jdUMfc3kAoy0PQ==", - "dev": true, - "license": "MIT" - }, "node_modules/karma-source-map-support": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", @@ -15251,238 +16128,10 @@ "source-map-support": "^0.5.5" } }, - "node_modules/karma/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, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/karma/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, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/karma/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/karma/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, - "license": "MIT", - "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/karma/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, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/karma/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, - "license": "MIT" - }, - "node_modules/karma/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, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/karma/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, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/karma/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, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/karma/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/karma/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/karma/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, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/karma/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, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/karma/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, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/karma/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, - "license": "MIT", - "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/karma/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, - "license": "MIT", - "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/karma/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, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/katex": { - "version": "0.16.25", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.25.tgz", - "integrity": "sha512-woHRUZ/iF23GBP1dkDQMh1QBad9dmr8/PAwNA54VrSOVYgI12MAcE14TqnDdQOdzyEonGzMepYnqBMYdsoAr8Q==", + "version": "0.16.38", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.38.tgz", + "integrity": "sha512-cjHooZUmIAUmDsHBN+1n8LaZdpmbj03LtYeYPyuYB7OuloiaeaV6N4LcfjcnHVzGWjVQmKrxxTrpDcmSzEZQwQ==", "funding": [ "https://opencollective.com/katex", "https://github.com/sponsors/katex" @@ -15522,13 +16171,6 @@ "node": ">=0.10.0" } }, - "node_modules/kolorist": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", - "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", - "license": "MIT", - "optional": true - }, "node_modules/langium": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/langium/-/langium-4.2.1.tgz", @@ -15625,27 +16267,13 @@ "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, "license": "MIT", - "optional": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/less/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "license": "MIT", - "optional": true, - "bin": { - "mime": "cli.js" + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/less/node_modules/pify": { @@ -15752,9 +16380,9 @@ } }, "node_modules/listr2/node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "dev": true, "license": "MIT" }, @@ -15857,24 +16485,6 @@ "node": ">= 12.13.0" } }, - "node_modules/local-pkg": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", - "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", - "license": "MIT", - "optional": true, - "dependencies": { - "mlly": "^1.7.4", - "pkg-types": "^2.3.0", - "quansync": "^0.2.11" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/locate-path": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", @@ -15895,7 +16505,6 @@ "version": "4.17.23", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", - "dev": true, "license": "MIT" }, "node_modules/lodash-es": { @@ -16137,23 +16746,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "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, - "license": "Apache-2.0", - "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.2", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", @@ -16249,6 +16841,13 @@ "node": ">=0.8.0" } }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -16269,6 +16868,49 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-asynchronous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/make-asynchronous/-/make-asynchronous-1.1.0.tgz", + "integrity": "sha512-ayF7iT+44LXdxJLTrTd3TLQpFDDvPCBxXxbv+pMUSuHA5Q8zyAfwkRP6aHHwNVFBUFWtxAHqwNJxF8vMZLAbVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-event": "^6.0.0", + "type-fest": "^4.6.0", + "web-worker": "^1.5.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-asynchronous/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -16353,12 +16995,18 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" } }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -16400,13 +17048,13 @@ } }, "node_modules/meow": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", - "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", "dev": true, "license": "MIT", "engines": { - "node": ">=16.10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -16440,28 +17088,29 @@ } }, "node_modules/mermaid": { - "version": "11.12.3", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.3.tgz", - "integrity": "sha512-wN5ZSgJQIC+CHJut9xaKWsknLxaFBwCPwPkGTSUYrTiHORWvpT8RxGk849HPnpUAQ+/9BPRqYb80jTpearrHzQ==", + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.13.0.tgz", + "integrity": "sha512-fEnci+Immw6lKMFI8sqzjlATTyjLkRa6axrEgLV2yHTfv8r+h1wjFbV6xeRtd4rUV1cS4EpR9rwp3Rci7TRWDw==", "license": "MIT", "optional": true, "dependencies": { "@braintree/sanitize-url": "^7.1.1", - "@iconify/utils": "^3.0.1", - "@mermaid-js/parser": "^1.0.0", + "@iconify/utils": "^3.0.2", + "@mermaid-js/parser": "^1.0.1", "@types/d3": "^7.4.3", - "cytoscape": "^3.29.3", + "@upsetjs/venn.js": "^2.0.0", + "cytoscape": "^3.33.1", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", - "dagre-d3-es": "7.0.13", - "dayjs": "^1.11.18", - "dompurify": "^3.2.5", - "katex": "^0.16.22", + "dagre-d3-es": "7.0.14", + "dayjs": "^1.11.19", + "dompurify": "^3.3.1", + "katex": "^0.16.25", "khroma": "^2.1.0", "lodash-es": "^4.17.23", - "marked": "^16.2.1", + "marked": "^16.3.0", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", @@ -16469,9 +17118,9 @@ } }, "node_modules/mermaid/node_modules/marked": { - "version": "16.4.1", - "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.1.tgz", - "integrity": "sha512-ntROs7RaN3EvWfy3EZi14H4YxmT6A5YvywfhO+0pm+cH/dnSQRmdAmoFIc3B9aiwTehyk7pESH4ofyBY+V5hZg==", + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", "license": "MIT", "optional": true, "bin": { @@ -16533,23 +17182,22 @@ } }, "node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true, "license": "MIT", "bin": { "mime": "cli.js" }, "engines": { - "node": ">=4.0.0" + "node": ">=4" } }, "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, "license": "MIT", "engines": { "node": ">= 0.6" @@ -16559,7 +17207,6 @@ "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, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -16620,16 +17267,16 @@ "license": "ISC" }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -16646,11 +17293,11 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -16799,48 +17446,29 @@ } }, "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==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true, "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, "bin": { "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/mlly": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", - "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.1.tgz", + "integrity": "sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ==", "license": "MIT", "optional": true, "dependencies": { - "acorn": "^8.15.0", + "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", - "ufo": "^1.6.1" - } - }, - "node_modules/mlly/node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "license": "MIT", - "optional": true - }, - "node_modules/mlly/node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" + "ufo": "^1.6.3" } }, "node_modules/moment": { @@ -16866,13 +17494,13 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/msgpackr": { - "version": "1.11.8", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.8.tgz", - "integrity": "sha512-bC4UGzHhVvgDNS7kn9tV8fAucIYUBuGojcaLiz7v+P63Lmtm0Xeji8B/8tYKddALXxJLpwIeBmUN3u64C4YkRA==", + "version": "1.11.9", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.9.tgz", + "integrity": "sha512-FkoAAyyA6HM8wL882EcEyFZ9s7hVADSwG9xrVx3dxxNQAtgADTrJoEWivID82Iv1zWDsv/OtbrrcZAzGzOMdNw==", "dev": true, "license": "MIT", "optional": true, @@ -17171,19 +17799,19 @@ } }, "node_modules/node-gyp/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/node-gyp/node_modules/tar": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", - "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", + "version": "7.5.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.11.tgz", + "integrity": "sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -17224,9 +17852,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", "dev": true, "license": "MIT" }, @@ -17247,39 +17875,42 @@ } }, "node_modules/normalize-package-data": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", - "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-8.0.0.tgz", + "integrity": "sha512-RWk+PI433eESQ7ounYxIp67CYuVsS1uYSonX3kA6ps/3LWfjVQa/ptEg6Y3T6uAMq1mWpX9PQ+qx+QaHpsc7gQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "hosted-git-info": "^7.0.0", + "hosted-git-info": "^9.0.0", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/normalize-package-data/node_modules/hosted-git-info": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", - "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", + "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", "dev": true, "license": "ISC", "dependencies": { - "lru-cache": "^10.0.1" + "lru-cache": "^11.1.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/normalize-package-data/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } }, "node_modules/normalize-path": { "version": "3.0.0", @@ -17302,22 +17933,22 @@ } }, "node_modules/normalize-url": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.1.tgz", - "integrity": "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-9.0.0.tgz", + "integrity": "sha512-z9nC87iaZXXySbWWtTHfCFJyFvKaUAW6lODhikG7ILSbVgmwuFjUqkgnheHvAUcGedO29e2QGBRXMUD64aurqQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=14.16" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/npm": { - "version": "11.7.0", - "resolved": "https://registry.npmjs.org/npm/-/npm-11.7.0.tgz", - "integrity": "sha512-wiCZpv/41bIobCoJ31NStIWKfAxxYyD1iYnWCtiyns8s5v3+l8y0HCP/sScuH6B5+GhIfda4HQKiqeGZwJWhFw==", + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-11.11.0.tgz", + "integrity": "sha512-82gRxKrh/eY5UnNorkTFcdBQAGpgjWehkfGVqAGlJjejEtJZGGJUqjo3mbBTNbc5BTnPKGVtGPBZGhElujX5cw==", "bundleDependencies": [ "@isaacs/string-locale-compare", "@npmcli/arborist", @@ -17335,7 +17966,6 @@ "cacache", "chalk", "ci-info", - "cli-columns", "fastest-levenshtein", "fs-minipass", "glob", @@ -17397,47 +18027,46 @@ ], "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^9.1.9", - "@npmcli/config": "^10.4.5", + "@npmcli/arborist": "^9.4.0", + "@npmcli/config": "^10.7.1", "@npmcli/fs": "^5.0.0", "@npmcli/map-workspaces": "^5.0.3", "@npmcli/metavuln-calculator": "^9.0.3", - "@npmcli/package-json": "^7.0.4", + "@npmcli/package-json": "^7.0.5", "@npmcli/promise-spawn": "^9.0.1", "@npmcli/redact": "^4.0.0", "@npmcli/run-script": "^10.0.3", - "@sigstore/tuf": "^4.0.0", + "@sigstore/tuf": "^4.0.1", "abbrev": "^4.0.0", "archy": "~1.0.0", "cacache": "^20.0.3", "chalk": "^5.6.2", - "ci-info": "^4.3.1", - "cli-columns": "^4.0.0", + "ci-info": "^4.4.0", "fastest-levenshtein": "^1.0.16", "fs-minipass": "^3.0.3", - "glob": "^13.0.0", + "glob": "^13.0.6", "graceful-fs": "^4.2.11", "hosted-git-info": "^9.0.2", "ini": "^6.0.0", - "init-package-json": "^8.2.4", - "is-cidr": "^6.0.1", + "init-package-json": "^8.2.5", + "is-cidr": "^6.0.3", "json-parse-even-better-errors": "^5.0.0", "libnpmaccess": "^10.0.3", - "libnpmdiff": "^8.0.12", - "libnpmexec": "^10.1.11", - "libnpmfund": "^7.0.12", + "libnpmdiff": "^8.1.3", + "libnpmexec": "^10.2.3", + "libnpmfund": "^7.0.17", "libnpmorg": "^8.0.1", - "libnpmpack": "^9.0.12", + "libnpmpack": "^9.1.3", "libnpmpublish": "^11.1.3", "libnpmsearch": "^9.0.1", "libnpmteam": "^8.0.2", "libnpmversion": "^8.0.3", - "make-fetch-happen": "^15.0.3", - "minimatch": "^10.1.1", - "minipass": "^7.1.1", + "make-fetch-happen": "^15.0.4", + "minimatch": "^10.2.2", + "minipass": "^7.1.3", "minipass-pipeline": "^1.2.4", "ms": "^2.1.2", - "node-gyp": "^12.1.0", + "node-gyp": "^12.2.0", "nopt": "^9.0.0", "npm-audit-report": "^7.0.0", "npm-install-checks": "^8.0.0", @@ -17447,21 +18076,21 @@ "npm-registry-fetch": "^19.1.1", "npm-user-validate": "^4.0.0", "p-map": "^7.0.4", - "pacote": "^21.0.4", + "pacote": "^21.4.0", "parse-conflict-json": "^5.0.1", "proc-log": "^6.1.0", "qrcode-terminal": "^0.12.0", "read": "^5.0.1", - "semver": "^7.7.3", + "semver": "^7.7.4", "spdx-expression-parse": "^4.0.0", - "ssri": "^13.0.0", + "ssri": "^13.0.1", "supports-color": "^10.2.2", - "tar": "^7.5.2", + "tar": "^7.5.9", "text-table": "~0.2.0", "tiny-relative-date": "^2.0.2", "treeverse": "^3.0.0", - "validate-npm-package-name": "^7.0.0", - "which": "^6.0.0" + "validate-npm-package-name": "^7.0.2", + "which": "^6.0.1" }, "bin": { "npm": "bin/npm-cli.js", @@ -17585,25 +18214,25 @@ "node": ">=8" } }, - "node_modules/npm/node_modules/@isaacs/balanced-match": { - "version": "4.0.1", + "node_modules/npm/node_modules/@gar/promise-retry": { + "version": "1.0.2", "dev": true, "inBundle": true, "license": "MIT", + "dependencies": { + "retry": "^0.13.1" + }, "engines": { - "node": "20 || >=22" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", + "node_modules/npm/node_modules/@gar/promise-retry/node_modules/retry": { + "version": "0.13.1", "dev": true, "inBundle": true, "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, "engines": { - "node": "20 || >=22" + "node": ">= 4" } }, "node_modules/npm/node_modules/@isaacs/fs-minipass": { @@ -17641,7 +18270,7 @@ } }, "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "9.1.9", + "version": "9.4.0", "dev": true, "inBundle": true, "license": "ISC", @@ -17659,7 +18288,7 @@ "@npmcli/run-script": "^10.0.0", "bin-links": "^6.0.0", "cacache": "^20.0.1", - "common-ancestor-path": "^1.0.1", + "common-ancestor-path": "^2.0.0", "hosted-git-info": "^9.0.0", "json-stringify-nice": "^1.1.4", "lru-cache": "^11.2.1", @@ -17688,7 +18317,7 @@ } }, "node_modules/npm/node_modules/@npmcli/config": { - "version": "10.4.5", + "version": "10.7.1", "dev": true, "inBundle": true, "license": "ISC", @@ -17719,17 +18348,17 @@ } }, "node_modules/npm/node_modules/@npmcli/git": { - "version": "7.0.1", + "version": "7.0.2", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { + "@gar/promise-retry": "^1.0.0", "@npmcli/promise-spawn": "^9.0.0", "ini": "^6.0.0", "lru-cache": "^11.2.1", "npm-pick-manifest": "^11.0.1", "proc-log": "^6.0.0", - "promise-retry": "^2.0.1", "semver": "^7.3.5", "which": "^6.0.0" }, @@ -17803,7 +18432,7 @@ } }, "node_modules/npm/node_modules/@npmcli/package-json": { - "version": "7.0.4", + "version": "7.0.5", "dev": true, "inBundle": true, "license": "ISC", @@ -17814,7 +18443,7 @@ "json-parse-even-better-errors": "^5.0.0", "proc-log": "^6.0.0", "semver": "^7.5.3", - "validate-npm-package-license": "^3.0.4" + "spdx-expression-parse": "^4.0.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" @@ -17883,7 +18512,7 @@ } }, "node_modules/npm/node_modules/@sigstore/core": { - "version": "3.0.0", + "version": "3.1.0", "dev": true, "inBundle": true, "license": "Apache-2.0", @@ -17901,52 +18530,43 @@ } }, "node_modules/npm/node_modules/@sigstore/sign": { - "version": "4.0.1", + "version": "4.1.0", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.0.0", + "@sigstore/core": "^3.1.0", "@sigstore/protobuf-specs": "^0.5.0", - "make-fetch-happen": "^15.0.2", - "proc-log": "^5.0.0", + "make-fetch-happen": "^15.0.3", + "proc-log": "^6.1.0", "promise-retry": "^2.0.1" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/@sigstore/sign/node_modules/proc-log": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/npm/node_modules/@sigstore/tuf": { - "version": "4.0.0", + "version": "4.0.1", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { "@sigstore/protobuf-specs": "^0.5.0", - "tuf-js": "^4.0.0" + "tuf-js": "^4.1.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@sigstore/verify": { - "version": "3.0.0", + "version": "3.1.0", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.0.0", + "@sigstore/core": "^3.1.0", "@sigstore/protobuf-specs": "^0.5.0" }, "engines": { @@ -17963,33 +18583,18 @@ } }, "node_modules/npm/node_modules/@tufjs/models": { - "version": "4.0.0", + "version": "4.1.0", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { "@tufjs/canonical-json": "2.0.0", - "minimatch": "^9.0.5" + "minimatch": "^10.1.1" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/@tufjs/models/node_modules/minimatch": { - "version": "9.0.5", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/npm/node_modules/abbrev": { "version": "4.0.0", "dev": true, @@ -18008,15 +18613,6 @@ "node": ">= 14" } }, - "node_modules/npm/node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/npm/node_modules/aproba": { "version": "2.1.0", "dev": true, @@ -18030,10 +18626,13 @@ "license": "MIT" }, "node_modules/npm/node_modules/balanced-match": { - "version": "1.0.2", + "version": "4.0.4", "dev": true, "inBundle": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } }, "node_modules/npm/node_modules/bin-links": { "version": "6.0.0", @@ -18064,12 +18663,15 @@ } }, "node_modules/npm/node_modules/brace-expansion": { - "version": "2.0.2", + "version": "5.0.3", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/npm/node_modules/cacache": { @@ -18116,7 +18718,7 @@ } }, "node_modules/npm/node_modules/ci-info": { - "version": "4.3.1", + "version": "4.4.0", "dev": true, "funding": [ { @@ -18131,30 +18733,14 @@ } }, "node_modules/npm/node_modules/cidr-regex": { - "version": "5.0.1", + "version": "5.0.3", "dev": true, "inBundle": true, "license": "BSD-2-Clause", - "dependencies": { - "ip-regex": "5.0.0" - }, "engines": { "node": ">=20" } }, - "node_modules/npm/node_modules/cli-columns": { - "version": "4.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/npm/node_modules/cmd-shim": { "version": "8.0.0", "dev": true, @@ -18165,10 +18751,13 @@ } }, "node_modules/npm/node_modules/common-ancestor-path": { - "version": "1.0.1", + "version": "2.0.0", "dev": true, "inBundle": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": ">= 18" + } }, "node_modules/npm/node_modules/cssesc": { "version": "3.0.0", @@ -18200,7 +18789,7 @@ } }, "node_modules/npm/node_modules/diff": { - "version": "8.0.2", + "version": "8.0.3", "dev": true, "inBundle": true, "license": "BSD-3-Clause", @@ -18208,22 +18797,6 @@ "node": ">=0.3.1" } }, - "node_modules/npm/node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/encoding": { - "version": "0.1.13", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, "node_modules/npm/node_modules/env-paths": { "version": "2.2.1", "dev": true, @@ -18267,17 +18840,17 @@ } }, "node_modules/npm/node_modules/glob": { - "version": "13.0.0", + "version": "13.0.6", "dev": true, "inBundle": true, "license": "BlueOak-1.0.0", "dependencies": { - "minimatch": "^10.1.1", - "minipass": "^7.1.2", - "path-scurry": "^2.0.0" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -18334,7 +18907,7 @@ } }, "node_modules/npm/node_modules/iconv-lite": { - "version": "0.6.3", + "version": "0.7.2", "dev": true, "inBundle": true, "license": "MIT", @@ -18344,6 +18917,10 @@ }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/npm/node_modules/ignore-walk": { @@ -18377,7 +18954,7 @@ } }, "node_modules/npm/node_modules/init-package-json": { - "version": "8.2.4", + "version": "8.2.5", "dev": true, "inBundle": true, "license": "ISC", @@ -18387,7 +18964,6 @@ "promzard": "^3.0.1", "read": "^5.0.1", "semver": "^7.7.2", - "validate-npm-package-license": "^3.0.4", "validate-npm-package-name": "^7.0.0" }, "engines": { @@ -18395,7 +18971,7 @@ } }, "node_modules/npm/node_modules/ip-address": { - "version": "10.0.1", + "version": "10.1.0", "dev": true, "inBundle": true, "license": "MIT", @@ -18403,46 +18979,25 @@ "node": ">= 12" } }, - "node_modules/npm/node_modules/ip-regex": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/npm/node_modules/is-cidr": { - "version": "6.0.1", + "version": "6.0.3", "dev": true, "inBundle": true, "license": "BSD-2-Clause", "dependencies": { - "cidr-regex": "5.0.1" + "cidr-regex": "^5.0.1" }, "engines": { "node": ">=20" } }, - "node_modules/npm/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/npm/node_modules/isexe": { - "version": "3.1.1", + "version": "4.0.0", "dev": true, "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=16" + "node": ">=20" } }, "node_modules/npm/node_modules/json-parse-even-better-errors": { @@ -18498,12 +19053,12 @@ } }, "node_modules/npm/node_modules/libnpmdiff": { - "version": "8.0.12", + "version": "8.1.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.1.9", + "@npmcli/arborist": "^9.4.0", "@npmcli/installed-package-contents": "^4.0.0", "binary-extensions": "^3.0.0", "diff": "^8.0.2", @@ -18517,19 +19072,19 @@ } }, "node_modules/npm/node_modules/libnpmexec": { - "version": "10.1.11", + "version": "10.2.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.1.9", + "@gar/promise-retry": "^1.0.0", + "@npmcli/arborist": "^9.4.0", "@npmcli/package-json": "^7.0.0", "@npmcli/run-script": "^10.0.0", "ci-info": "^4.0.0", "npm-package-arg": "^13.0.0", "pacote": "^21.0.2", "proc-log": "^6.0.0", - "promise-retry": "^2.0.1", "read": "^5.0.1", "semver": "^7.3.7", "signal-exit": "^4.1.0", @@ -18540,12 +19095,12 @@ } }, "node_modules/npm/node_modules/libnpmfund": { - "version": "7.0.12", + "version": "7.0.17", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.1.9" + "@npmcli/arborist": "^9.4.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" @@ -18565,12 +19120,12 @@ } }, "node_modules/npm/node_modules/libnpmpack": { - "version": "9.0.12", + "version": "9.1.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.1.9", + "@npmcli/arborist": "^9.4.0", "@npmcli/run-script": "^10.0.0", "npm-package-arg": "^13.0.0", "pacote": "^21.0.2" @@ -18640,20 +19195,21 @@ } }, "node_modules/npm/node_modules/lru-cache": { - "version": "11.2.2", + "version": "11.2.6", "dev": true, "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" } }, "node_modules/npm/node_modules/make-fetch-happen": { - "version": "15.0.3", + "version": "15.0.4", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { + "@gar/promise-retry": "^1.0.0", "@npmcli/agent": "^4.0.0", "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", @@ -18663,7 +19219,6 @@ "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^6.0.0", - "promise-retry": "^2.0.1", "ssri": "^13.0.0" }, "engines": { @@ -18671,25 +19226,25 @@ } }, "node_modules/npm/node_modules/minimatch": { - "version": "10.1.1", + "version": "10.2.2", "dev": true, "inBundle": true, "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/npm/node_modules/minipass": { - "version": "7.1.2", + "version": "7.1.3", "dev": true, "inBundle": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -18707,20 +19262,20 @@ } }, "node_modules/npm/node_modules/minipass-fetch": { - "version": "5.0.0", + "version": "5.0.2", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", + "minipass-sized": "^2.0.0", "minizlib": "^3.0.1" }, "engines": { "node": "^20.17.0 || >=22.9.0" }, "optionalDependencies": { - "encoding": "^0.1.13" + "iconv-lite": "^0.7.2" } }, "node_modules/npm/node_modules/minipass-flush": { @@ -18747,6 +19302,12 @@ "node": ">=8" } }, + "node_modules/npm/node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, "node_modules/npm/node_modules/minipass-pipeline": { "version": "1.2.4", "dev": true, @@ -18771,25 +19332,19 @@ "node": ">=8" } }, - "node_modules/npm/node_modules/minipass-sized": { - "version": "1.0.3", + "node_modules/npm/node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", "dev": true, "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "ISC" }, - "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { - "version": "3.3.6", + "node_modules/npm/node_modules/minipass-sized": { + "version": "2.0.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "yallist": "^4.0.0" + "minipass": "^7.1.2" }, "engines": { "node": ">=8" @@ -18832,7 +19387,7 @@ } }, "node_modules/npm/node_modules/node-gyp": { - "version": "12.1.0", + "version": "12.2.0", "dev": true, "inBundle": true, "license": "MIT", @@ -18844,7 +19399,7 @@ "nopt": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", - "tar": "^7.5.2", + "tar": "^7.5.4", "tinyglobby": "^0.2.12", "which": "^6.0.0" }, @@ -18928,7 +19483,7 @@ } }, "node_modules/npm/node_modules/npm-packlist": { - "version": "10.0.3", + "version": "10.0.4", "dev": true, "inBundle": true, "license": "ISC", @@ -19009,11 +19564,12 @@ } }, "node_modules/npm/node_modules/pacote": { - "version": "21.0.4", + "version": "21.4.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { + "@gar/promise-retry": "^1.0.0", "@npmcli/git": "^7.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/package-json": "^7.0.0", @@ -19027,7 +19583,6 @@ "npm-pick-manifest": "^11.0.1", "npm-registry-fetch": "^19.0.0", "proc-log": "^6.0.0", - "promise-retry": "^2.0.1", "sigstore": "^4.0.0", "ssri": "^13.0.0", "tar": "^7.4.3" @@ -19054,7 +19609,7 @@ } }, "node_modules/npm/node_modules/path-scurry": { - "version": "2.0.0", + "version": "2.0.2", "dev": true, "inBundle": true, "license": "BlueOak-1.0.0", @@ -19063,14 +19618,14 @@ "minipass": "^7.1.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/npm/node_modules/postcss-selector-parser": { - "version": "7.1.0", + "version": "7.1.1", "dev": true, "inBundle": true, "license": "MIT", @@ -19189,7 +19744,7 @@ "optional": true }, "node_modules/npm/node_modules/semver": { - "version": "7.7.3", + "version": "7.7.4", "dev": true, "inBundle": true, "license": "ISC", @@ -19213,17 +19768,17 @@ } }, "node_modules/npm/node_modules/sigstore": { - "version": "4.0.0", + "version": "4.1.0", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.0.0", + "@sigstore/core": "^3.1.0", "@sigstore/protobuf-specs": "^0.5.0", - "@sigstore/sign": "^4.0.0", - "@sigstore/tuf": "^4.0.0", - "@sigstore/verify": "^3.0.0" + "@sigstore/sign": "^4.1.0", + "@sigstore/tuf": "^4.0.1", + "@sigstore/verify": "^3.1.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" @@ -19267,26 +19822,6 @@ "node": ">= 14" } }, - "node_modules/npm/node_modules/spdx-correct": { - "version": "3.2.0", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, "node_modules/npm/node_modules/spdx-exceptions": { "version": "2.5.0", "dev": true, @@ -19304,13 +19839,13 @@ } }, "node_modules/npm/node_modules/spdx-license-ids": { - "version": "3.0.22", + "version": "3.0.23", "dev": true, "inBundle": true, "license": "CC0-1.0" }, "node_modules/npm/node_modules/ssri": { - "version": "13.0.0", + "version": "13.0.1", "dev": true, "inBundle": true, "license": "ISC", @@ -19321,32 +19856,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/string-width": { - "version": "4.2.3", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/npm/node_modules/supports-color": { "version": "10.2.2", "dev": true, @@ -19360,7 +19869,7 @@ } }, "node_modules/npm/node_modules/tar": { - "version": "7.5.2", + "version": "7.5.9", "dev": true, "inBundle": true, "license": "BlueOak-1.0.0", @@ -19375,15 +19884,6 @@ "node": ">=18" } }, - "node_modules/npm/node_modules/tar/node_modules/yallist": { - "version": "5.0.0", - "dev": true, - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/npm/node_modules/text-table": { "version": "0.2.0", "dev": true, @@ -19451,14 +19951,14 @@ } }, "node_modules/npm/node_modules/tuf-js": { - "version": "4.0.0", + "version": "4.1.0", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "@tufjs/models": "4.0.0", - "debug": "^4.4.1", - "make-fetch-happen": "^15.0.0" + "@tufjs/models": "4.1.0", + "debug": "^4.4.3", + "make-fetch-happen": "^15.0.1" }, "engines": { "node": "^20.17.0 || >=22.9.0" @@ -19494,28 +19994,8 @@ "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/validate-npm-package-license": { - "version": "3.0.4", - "dev": true, - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { - "version": "3.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, "node_modules/npm/node_modules/validate-npm-package-name": { - "version": "7.0.0", + "version": "7.0.2", "dev": true, "inBundle": true, "license": "ISC", @@ -19533,12 +20013,12 @@ } }, "node_modules/npm/node_modules/which": { - "version": "6.0.0", + "version": "6.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "isexe": "^3.1.1" + "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" @@ -19561,10 +20041,13 @@ } }, "node_modules/npm/node_modules/yallist": { - "version": "4.0.0", + "version": "5.0.0", "dev": true, "inBundle": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } }, "node_modules/nth-check": { "version": "2.1.1", @@ -19840,6 +20323,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-event": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-6.0.1.tgz", + "integrity": "sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-timeout": "^6.1.2" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-filter": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-4.1.0.tgz", @@ -19899,9 +20398,9 @@ } }, "node_modules/p-map": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", - "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", "dev": true, "license": "MIT", "engines": { @@ -19952,6 +20451,19 @@ "node": ">= 4" } }, + "node_modules/p-timeout": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", + "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", @@ -19970,9 +20482,9 @@ "license": "BlueOak-1.0.0" }, "node_modules/package-manager-detector": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.5.0.tgz", - "integrity": "sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", "license": "MIT", "optional": true }, @@ -20149,6 +20661,13 @@ "node": ">= 0.8" } }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, "node_modules/path-data-parser": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", @@ -20241,13 +20760,23 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, "license": "MIT", - "optional": true + "engines": { + "node": ">= 14.16" + } }, "node_modules/photoviewer": { - "version": "3.10.3", - "resolved": "https://registry.npmjs.org/photoviewer/-/photoviewer-3.10.3.tgz", - "integrity": "sha512-6RqhMOiPVMsgfMTP/j/lQLGGj2glzBWIDnrTpx2rcACOcO8ig+bnBAfofEPXBmLOgKxsu+HIiLQudVtloS9GEA==", + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/photoviewer/-/photoviewer-3.11.1.tgz", + "integrity": "sha512-CnD62Zs0rmOd6y4prQmbF4/pB2zXhrxLn+5io4AVjSXLAAXjw2tHugOOZocV8r4MZ7QvUTB840L2a8MqJgEV8g==", "license": "MIT", "dependencies": { "domq.js": "^0.7.0" @@ -20404,15 +20933,15 @@ } }, "node_modules/pkg-types": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", "license": "MIT", "optional": true, "dependencies": { - "confbox": "^0.2.2", - "exsolve": "^1.0.7", - "pathe": "^2.0.3" + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" } }, "node_modules/points-on-curve": { @@ -20575,9 +21104,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "dev": true, "license": "MIT", "dependencies": { @@ -20696,9 +21225,9 @@ } }, "node_modules/prettier-eslint/node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -20827,9 +21356,9 @@ } }, "node_modules/prettier-eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -20860,13 +21389,30 @@ "dev": true, "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prettier-eslint/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, + "license": "MIT" + }, + "node_modules/prettier-eslint/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" } }, "node_modules/prettier-eslint/node_modules/chalk": { @@ -20972,9 +21518,9 @@ } }, "node_modules/prettier-eslint/node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -21353,6 +21899,12 @@ "node": ">= 0.10" } }, + "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==", + "license": "MIT" + }, "node_modules/prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -21362,9 +21914,9 @@ "optional": true }, "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", "dev": true, "license": "MIT", "dependencies": { @@ -21373,20 +21925,13 @@ } }, "node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", - "dev": true, - "license": "MIT" - }, - "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==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.9" + "node": ">=6" } }, "node_modules/qs": { @@ -21405,23 +21950,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/quansync": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", - "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/antfu" - }, - { - "type": "individual", - "url": "https://github.com/sponsors/sxzz" - } - ], - "license": "MIT", - "optional": true - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -21533,38 +22061,38 @@ "license": "MIT" }, "node_modules/read-package-up": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", - "integrity": "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-12.0.0.tgz", + "integrity": "sha512-Q5hMVBYur/eQNWDdbF4/Wqqr9Bjvtrw2kjGxxBbKLbx8bVCL8gcArjTy8zDUuLGQicftpMuU0riQNcAsbtOVsw==", "dev": true, "license": "MIT", "dependencies": { - "find-up-simple": "^1.0.0", - "read-pkg": "^9.0.0", - "type-fest": "^4.6.0" + "find-up-simple": "^1.0.1", + "read-pkg": "^10.0.0", + "type-fest": "^5.2.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/read-pkg": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", - "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-10.1.0.tgz", + "integrity": "sha512-I8g2lArQiP78ll51UeMZojewtYgIRCKCWqZEgOO8c/uefTI+XDXvCSXu3+YNUaTNvZzobrL5+SqHjBrByRRTdg==", "dev": true, "license": "MIT", "dependencies": { - "@types/normalize-package-data": "^2.4.3", - "normalize-package-data": "^6.0.0", - "parse-json": "^8.0.0", - "type-fest": "^4.6.0", - "unicorn-magic": "^0.1.0" + "@types/normalize-package-data": "^2.4.4", + "normalize-package-data": "^8.0.0", + "parse-json": "^8.3.0", + "type-fest": "^5.4.4", + "unicorn-magic": "^0.4.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -21588,6 +22116,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/read-pkg/node_modules/parse-json/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/unicorn-magic": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz", + "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -21894,6 +22448,59 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/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, + "license": "MIT" + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "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/rimraf/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/robust-predicates": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", @@ -22033,24 +22640,6 @@ ], "license": "MIT" }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -22059,14 +22648,14 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.85.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.85.0.tgz", - "integrity": "sha512-3ToiC1xZ1Y8aU7+CkgCI/tqyuPXEmYGJXO7H4uqp0xkLXUqp88rQQ4j1HmP37xSJLbCJPaIiv+cT1y+grssrww==", + "version": "1.98.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.98.0.tgz", + "integrity": "sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A==", "dev": true, "license": "MIT", "dependencies": { "chokidar": "^4.0.0", - "immutable": "^5.0.2", + "immutable": "^5.1.5", "source-map-js": ">=0.6.2 <2.0.0" }, "bin": { @@ -22121,12 +22710,28 @@ } }, "node_modules/sax": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", - "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.5.0.tgz", + "integrity": "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==", + "dev": true, + "license": "BlueOak-1.0.0", + "optional": true, + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", "dev": true, "license": "ISC", - "optional": true + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } }, "node_modules/schema-utils": { "version": "4.3.3", @@ -22289,9 +22894,9 @@ } }, "node_modules/semantic-release/node_modules/execa": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.0.tgz", - "integrity": "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", "dev": true, "license": "MIT", "dependencies": { @@ -22382,30 +22987,15 @@ } }, "node_modules/semantic-release/node_modules/lru-cache": { - "version": "11.2.4", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", - "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" } }, - "node_modules/semantic-release/node_modules/normalize-package-data": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-8.0.0.tgz", - "integrity": "sha512-RWk+PI433eESQ7ounYxIp67CYuVsS1uYSonX3kA6ps/3LWfjVQa/ptEg6Y3T6uAMq1mWpX9PQ+qx+QaHpsc7gQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^9.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/semantic-release/node_modules/npm-run-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", @@ -22413,122 +23003,37 @@ "dev": true, "license": "MIT", "dependencies": { - "path-key": "^4.0.0", - "unicorn-magic": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/semantic-release/node_modules/parse-json": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", - "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.26.2", - "index-to-position": "^1.1.0", - "type-fest": "^4.39.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/semantic-release/node_modules/parse-json/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/semantic-release/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/semantic-release/node_modules/read-package-up": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-12.0.0.tgz", - "integrity": "sha512-Q5hMVBYur/eQNWDdbF4/Wqqr9Bjvtrw2kjGxxBbKLbx8bVCL8gcArjTy8zDUuLGQicftpMuU0riQNcAsbtOVsw==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up-simple": "^1.0.1", - "read-pkg": "^10.0.0", - "type-fest": "^5.2.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/semantic-release/node_modules/read-pkg": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-10.0.0.tgz", - "integrity": "sha512-A70UlgfNdKI5NSvTTfHzLQj7NJRpJ4mT5tGafkllJ4wh71oYuGm/pzphHcmW4s35iox56KSK721AihodoXSc/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/normalize-package-data": "^2.4.4", - "normalize-package-data": "^8.0.0", - "parse-json": "^8.3.0", - "type-fest": "^5.2.0", + "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" }, "engines": { - "node": ">=20" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/node_modules/strip-final-newline": { + "node_modules/semantic-release/node_modules/path-key": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", - "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/node_modules/type-fest": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.4.1.tgz", - "integrity": "sha512-xygQcmneDyzsEuKZrFbRMne5HDqMs++aFzefrJTgEIKjQ3rekM+RPfFCVq2Gp1VIDqddoYeppCj4Pcb+RZW0GQ==", + "node_modules/semantic-release/node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", "dev": true, - "license": "(MIT OR CC0-1.0)", - "dependencies": { - "tagged-tag": "^1.0.0" - }, + "license": "MIT", "engines": { - "node": ">=20" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -22606,26 +23111,6 @@ "node": ">=10" } }, - "node_modules/semver-dsl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/semver-dsl/-/semver-dsl-1.0.1.tgz", - "integrity": "sha512-e8BOaTo007E3dMuQQTnPdalbKTABKNS7UxoBIDnwOqRa+QwMrCPjynB8zAlPF6xlqUfdLPPLIJ13hJNmhtq8Ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^5.3.0" - } - }, - "node_modules/semver-dsl/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, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, "node_modules/semver-regex": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz", @@ -22681,39 +23166,6 @@ "dev": true, "license": "MIT" }, - "node_modules/send/node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/send/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -22791,6 +23243,16 @@ "dev": true, "license": "MIT" }, + "node_modules/serve-index/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, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/serve-static": { "version": "1.16.3", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", @@ -22807,16 +23269,6 @@ "node": ">= 0.8.0" } }, - "node_modules/serve-static/node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -22949,6 +23401,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -23086,6 +23545,21 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/skin-tone": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", @@ -23153,104 +23627,6 @@ "npm": ">= 3.0.0" } }, - "node_modules/socket.io": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", - "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "cors": "~2.8.5", - "debug": "~4.3.2", - "engine.io": "~6.6.0", - "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, - "license": "MIT", - "dependencies": { - "debug": "~4.3.4", - "ws": "~8.17.1" - } - }, - "node_modules/socket.io-adapter/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "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, - "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socket.io-parser/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -23304,13 +23680,14 @@ } }, "node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "engines": { - "node": ">= 8" + "node": ">= 12" } }, "node_modules/source-map-js": { @@ -23415,9 +23792,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.22", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", - "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", "dev": true, "license": "CC0-1.0" }, @@ -23463,13 +23840,6 @@ "node": ">= 10.x" } }, - "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, - "license": "BSD-3-Clause" - }, "node_modules/ssf": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", @@ -23495,16 +23865,30 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "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==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, "node_modules/stream-combiner2": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", @@ -23549,56 +23933,6 @@ "safe-buffer": "~5.1.0" } }, - "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, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "MIT", - "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, - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -23684,13 +24018,13 @@ } }, "node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -23756,6 +24090,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/stylis": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", @@ -23764,13 +24118,14 @@ "optional": true }, "node_modules/super-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.0.0.tgz", - "integrity": "sha512-CY8u7DtbvucKuquCmOFEKhr9Besln7n9uN8eFbwcoGYWXOMW07u2o8njWaiXt11ylS3qoGF55pILjRmPlbodyg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.1.0.tgz", + "integrity": "sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ==", "dev": true, "license": "MIT", "dependencies": { "function-timeout": "^1.0.1", + "make-asynchronous": "^1.0.1", "time-span": "^5.1.0" }, "engines": { @@ -23833,6 +24188,13 @@ "node": ">=0.10" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/synckit": { "version": "0.11.12", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", @@ -23880,6 +24242,7 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -23957,19 +24320,6 @@ "node": ">=8" } }, - "node_modules/tar/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/tar/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -23988,9 +24338,9 @@ } }, "node_modules/tempy": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.1.0.tgz", - "integrity": "sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.2.0.tgz", + "integrity": "sha512-d79HhZya5Djd7am0q+W4RTsSU+D/aJzM+4Y4AGJGuGlgM2L6sx5ZvOYTmZjqPhrDrV6xJTtRSm1JCLj6V6LHLQ==", "dev": true, "license": "MIT", "dependencies": { @@ -24052,9 +24402,9 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.17", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.17.tgz", - "integrity": "sha512-YR7PtUp6GMU91BgSJmlaX/rS2lGDbAF7D+Wtq7hRO+MiljNmodYvqslzCFiYVAgW+Qoaaia/QUIP4lGXufjdZw==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", + "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", "dev": true, "license": "MIT", "dependencies": { @@ -24092,6 +24442,21 @@ "dev": true, "license": "MIT" }, + "node_modules/test-exclude": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", + "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^10.2.2" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/text-extensions": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz", @@ -24233,12 +24598,22 @@ "license": "MIT", "optional": true }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, "node_modules/tinyexec": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", - "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", "devOptional": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/tinyglobby": { "version": "0.2.15", @@ -24270,16 +24645,56 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/tmp": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", - "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", "dev": true, "license": "MIT", "engines": { - "node": ">=14.14" + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.0.25", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.25.tgz", + "integrity": "sha512-keinCnPbwXEUG3ilrWQZU+CqcTTzHq9m2HhoUP2l7Xmi8l1LuijAXLpAJ5zRW+ifKTNscs4NdCkfkDCBYm352w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.25" + }, + "bin": { + "tldts": "bin/cli.js" } }, + "node_modules/tldts-core": { + "version": "7.0.25", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.25.tgz", + "integrity": "sha512-ZjCZK0rppSBu7rjHYDYsEaMOIbbT+nWF57hKkv4IUmZWBNrBWBOjIElc0mKRgLM8bm7x/BBlof6t2gi/Oq/Asw==", + "dev": true, + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -24303,6 +24718,42 @@ "node": ">=0.6" } }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/traverse": { "version": "0.6.8", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.8.tgz", @@ -24366,6 +24817,17 @@ "node": ">=6.10" } }, + "node_modules/ts-morph": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-21.0.1.tgz", + "integrity": "sha512-dbDtVdEAncKctzrVZ+Nr7kHpHkv+0JDJb2MjjpBaj8bFeCkePU9rHfMklmhuLFnpeq/EJZk2IhStY6NzqgjOkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ts-morph/common": "~0.22.0", + "code-block-writer": "^12.0.0" + } + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -24378,251 +24840,42 @@ "@tsconfig/node12": "^1.0.7", "@tsconfig/node14": "^1.0.0", "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/tslint": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", - "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", - "deprecated": "TSLint has been deprecated in favor of ESLint. Please see https://github.com/palantir/tslint/issues/4534 for more information.", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "builtin-modules": "^1.1.1", - "chalk": "^2.3.0", - "commander": "^2.12.1", - "diff": "^4.0.1", - "glob": "^7.1.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.3", - "resolve": "^1.3.2", - "semver": "^5.3.0", - "tslib": "^1.13.0", - "tsutils": "^2.29.0" - }, - "bin": { - "tslint": "bin/tslint" - }, - "engines": { - "node": ">=4.8.0" - }, - "peerDependencies": { - "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 4.0.0-dev" - } - }, - "node_modules/tslint/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, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/tslint/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/tslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/tslint/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, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/tslint/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, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/tslint/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, - "license": "MIT" - }, - "node_modules/tslint/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, - "license": "MIT" - }, - "node_modules/tslint/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, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/tslint/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, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/tslint/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/tslint/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, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tslint/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, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/tslint/node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/tslint/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, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/tslint/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true, - "license": "0BSD" - }, - "node_modules/tsutils": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", - "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^1.8.1" + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" }, "peerDependencies": { - "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev" + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } } }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, "node_modules/tuf-js": { @@ -24664,13 +24917,16 @@ } }, "node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.4.4.tgz", + "integrity": "sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==", "dev": true, "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, "engines": { - "node": ">=16" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -24712,16 +24968,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.53.1.tgz", - "integrity": "sha512-gB+EVQfP5RDElh9ittfXlhZJdjSU4jUSTyE2+ia8CYyNvet4ElfaLlAIqDvQV9JPknKx0jQH1racTYe/4LaLSg==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.0.tgz", + "integrity": "sha512-W8GcigEMEeB07xEZol8oJ26rigm3+bfPHxHvwbYUlu1fUDsGuQ7Hiskx5xGW/xM4USc9Ephe3jtv7ZYPQntHeA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.53.1", - "@typescript-eslint/parser": "8.53.1", - "@typescript-eslint/typescript-estree": "8.53.1", - "@typescript-eslint/utils": "8.53.1" + "@typescript-eslint/eslint-plugin": "8.57.0", + "@typescript-eslint/parser": "8.57.0", + "@typescript-eslint/typescript-estree": "8.57.0", + "@typescript-eslint/utils": "8.57.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -24731,41 +24987,14 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/ua-parser-js": { - "version": "0.7.41", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.41.tgz", - "integrity": "sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg==", - "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" - } - ], - "license": "MIT", - "bin": { - "ua-parser-js": "script/cli.js" - }, - "engines": { - "node": "*" - } - }, "node_modules/ufo": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", - "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", "license": "MIT", "optional": true }, @@ -24911,6 +25140,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/universal-user-agent": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", @@ -24979,16 +25222,6 @@ "punycode": "^2.1.0" } }, - "node_modules/uri-js/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, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/url-join": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz", @@ -25067,6 +25300,36 @@ "node": ">= 0.8" } }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vite": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", @@ -25142,6 +25405,29 @@ } } }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/vite/node_modules/postcss": { "version": "8.5.8", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", @@ -25171,22 +25457,92 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, "node_modules/vlq": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", "license": "MIT" }, - "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, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/vscode-jsonrpc": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", @@ -25302,6 +25658,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/watchpack": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", @@ -25344,6 +25713,23 @@ "license": "MIT", "optional": true }, + "node_modules/web-worker": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz", + "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, "node_modules/webpack": { "version": "5.105.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", @@ -25583,28 +25969,6 @@ "node": ">=8.10.0" } }, - "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", - "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", - "dev": true, - "license": "MIT", - "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/webpack-merge": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", @@ -25621,9 +25985,9 @@ } }, "node_modules/webpack-sources": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", - "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", + "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", "dev": true, "license": "MIT", "engines": { @@ -25652,6 +26016,13 @@ } } }, + "node_modules/webpack/node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT" + }, "node_modules/webpack/node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -25722,6 +26093,30 @@ "node": ">=0.8.0" } }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", + "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -25738,6 +26133,23 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wildcard": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", @@ -25964,9 +26376,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "dev": true, "license": "MIT", "engines": { @@ -26006,6 +26418,23 @@ "node": ">=0.8" } }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -26128,9 +26557,9 @@ } }, "node_modules/yocto-queue": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", - "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", "dev": true, "license": "MIT", "engines": { @@ -26166,6 +26595,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/zone.js": { "version": "0.15.1", "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.1.tgz", diff --git a/package.json b/package.json index 5441cf7f1..749e6c01e 100644 --- a/package.json +++ b/package.json @@ -23,13 +23,16 @@ "start": "ng serve", "build": "ng build", "build-prod": "ng build --configuration production", + "test": "vitest run", + "test:watch": "vitest watch", + "test:ui": "vitest --ui", + "test:coverage": "vitest run --coverage", "build:analyze": "ng build --configuration analyze --stats-json", "analyze:report": "npm run build:analyze && npx esbuild-visualizer --metadata dist/app/stats.json --filename dist/app/esbuild-report.html && open dist/app/esbuild-report.html", - "test": "ng test", - "e2e": "ng e2e", "prepare": "husky || true", "lint": "npx eslint src", "lint:fix": "npm run lint -- --fix", + "lint:warnonly": "npm run lint || true", "prettier": "npx prettier src --check", "prettier:fix": "npm run prettier -- --write", "format": "npm run prettier:fix && npm run lint:fix", @@ -47,9 +50,11 @@ "@angular/platform-browser": "^19.2.19", "@angular/platform-browser-dynamic": "^19.2.19", "@angular/router": "^19.2.19", + "@mitre-attack/attack-data-model": "^5.0.0-beta.1", "@ng-matero/extensions": "^19.5.1", "diff-match-patch-ts": "^0.8.0", "jdenticon": "^3.2.0", + "lodash": "^4.17.21", "marked": "^15.0.11", "moment": "^2.29.4", "ngx-autosize": "^2.0.4", @@ -63,16 +68,18 @@ "zone.js": "~0.15.0" }, "devDependencies": { + "@analogjs/vite-plugin-angular": "^1.22.1", + "@analogjs/vitest-angular": "^1.22.1", + "@eslint/js": "^9.39.1", "@angular-devkit/build-angular": "^19.2.22", "@angular/cli": "^19.2.22", "@angular/compiler-cli": "^19.2.19", "@codedependant/semantic-release-docker": "^5.1.1", "@commitlint/config-conventional": "^20.4.3", - "@types/jasmine": "^6.0.0", - "@types/jasminewd2": "^2.0.10", "@types/node": "^20.12.11", + "@vitest/coverage-v8": "^3.2.4", + "@vitest/ui": "^3.2.4", "angular-eslint": "^19.4.0", - "codelyzer": "^6.0.0", "commitlint": "^19.8.0", "esbuild-visualizer": "^0.7.0", "eslint": "^9.23.0", @@ -80,19 +87,15 @@ "eslint-plugin-prettier": "^5.5.5", "eslint-plugin-unused-imports": "^4.1.4", "husky": "^9.1.7", - "jasmine-core": "^5.1.1", - "jasmine-spec-reporter": "^7.0.0", - "karma": "~6.4.2", - "karma-chrome-launcher": "^3.2.0", - "karma-coverage-istanbul-reporter": "~3.0.2", - "karma-jasmine": "^5.1.0", - "karma-jasmine-html-reporter": "^2.1.0", + "jsdom": "^27.0.1", + "sass": "^1.94.2", "prettier": "^3.8.1", "prettier-eslint": "^16.3.0", "semantic-release": "^25.0.3", "ts-node": "^10.9.1", - "tslint": "~6.1.0", "typescript": "^5.8.3", - "typescript-eslint": "^8.53.1" + "typescript-eslint": "^8.53.1", + "vite": "^6.4.1", + "vitest": "^3.2.4" } } diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index f974744cc..ea3e93aa0 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -3,6 +3,7 @@ import { Routes, RouterModule } from '@angular/router'; import { LandingPageComponent } from './views/landing-page/landing-page.component'; import { HelpPageComponent } from './views/help-page/help-page.component'; import { DashboardPageComponent } from './views/dashboard-page/dashboard-page.component'; +import { DataQualityComponent } from './views/dashboard-page/data-quality/data-quality.component'; import { OrgSettingsPageComponent } from './views/dashboard-page/org-settings-page/org-settings-page.component'; import { UserAccountsPageComponent } from './views/dashboard-page/user-accounts-page/user-accounts-page.component'; import { DefaultMarkingDefinitionsComponent } from './views/dashboard-page/default-marking-definitions/default-marking-definitions.component'; @@ -53,30 +54,6 @@ const routes: Routes = [ }, component: DashboardPageComponent, }, - { - path: 'org-settings', - data: { - breadcrumb: 'organization settings', - title: 'Organization Identity', - }, - component: OrgSettingsPageComponent, - }, - { - path: 'user-accounts', - data: { - breadcrumb: 'user accounts', - title: 'User Accounts', - }, - component: UserAccountsPageComponent, - }, - { - path: 'default-marking-definitions', - data: { - breadcrumb: 'default marking definitions', - title: 'Default Marking Definitions', - }, - component: DefaultMarkingDefinitionsComponent, - }, { path: 'teams', data: { @@ -104,6 +81,42 @@ const routes: Routes = [ }, ], }, + { + path: 'data-quality', + data: { + breadcrumb: 'data quality', + title: 'Data Quality', + roles: [Role.ADMIN, Role.TEAM_LEAD], + }, + component: DataQualityComponent, + }, + { + path: 'org-settings', + data: { + breadcrumb: 'organization settings', + title: 'Organization Identity', + roles: [Role.ADMIN], + }, + component: OrgSettingsPageComponent, + }, + { + path: 'user-accounts', + data: { + breadcrumb: 'user accounts', + title: 'User Accounts', + roles: [Role.ADMIN], + }, + component: UserAccountsPageComponent, + }, + { + path: 'default-marking-definitions', + data: { + breadcrumb: 'default marking definitions', + title: 'Default Marking Definitions', + roles: [Role.ADMIN], + }, + component: DefaultMarkingDefinitionsComponent, + }, ], }, { diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 62204e889..cbf208613 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -1,12 +1,33 @@ +import { provideHttpClient } from '@angular/common/http'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; +import { provideRouter } from '@angular/router'; +import { LoggerModule, NgxLoggerLevel } from 'ngx-logger'; +import { vi } from 'vitest'; import { AppComponent } from './app.component'; describe('AppComponent', () => { beforeEach(async () => { + // Mock window.matchMedia + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: vi.fn().mockImplementation(query => ({ + matches: false, + media: query, + onchange: null, + addListener: vi.fn(), + removeListener: vi.fn(), + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + })), + }); + await TestBed.configureTestingModule({ - imports: [RouterTestingModule], + imports: [LoggerModule.forRoot({ level: NgxLoggerLevel.OFF })], declarations: [AppComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [provideHttpClient(), provideRouter([])], }).compileComponents(); }); @@ -15,19 +36,4 @@ describe('AppComponent', () => { const app = fixture.componentInstance; expect(app).toBeTruthy(); }); - - it(`should have as title 'app'`, () => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.componentInstance; - expect(app.title).toEqual('app'); - }); - - it('should render title', () => { - const fixture = TestBed.createComponent(AppComponent); - fixture.detectChanges(); - const compiled = fixture.nativeElement; - expect(compiled.querySelector('.content span').textContent).toContain( - 'app app is running!' - ); - }); }); diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a80dd6e42..54b5bb72b 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -106,10 +106,7 @@ import { TlpEditComponent } from './components/stix/tlp-property/tlp-edit/tlp-ed import { TlpPropertyComponent } from './components/stix/tlp-property/tlp-property.component'; import { TlpViewComponent } from './components/stix/tlp-property/tlp-view/tlp-view.component'; -import { AttackidDiffComponent } from './components/stix/attackid-property/attackid-diff/attackid-diff.component'; -import { AttackIDEditComponent } from './components/stix/attackid-property/attackid-edit/attackid-edit.component'; import { AttackIDPropertyComponent } from './components/stix/attackid-property/attackid-property.component'; -import { AttackIDViewComponent } from './components/stix/attackid-property/attackid-view/attackid-view.component'; import { StixIDPropertyComponent } from './components/stix/stixid-property/stixid-property.component'; @@ -158,6 +155,7 @@ import { CitationViewComponent } from './components/stix/citation-property/citat import { HelpPageComponent } from './views/help-page/help-page.component'; import { LandingPageComponent } from './views/landing-page/landing-page.component'; import { DashboardPageComponent } from './views/dashboard-page/dashboard-page.component'; +import { DataQualityComponent } from './views/dashboard-page/data-quality/data-quality.component'; import { OrgSettingsPageComponent } from './views/dashboard-page/org-settings-page/org-settings-page.component'; import { UserAccountsPageComponent } from './views/dashboard-page/user-accounts-page/user-accounts-page.component'; import { DefaultMarkingDefinitionsComponent } from './views/dashboard-page/default-marking-definitions/default-marking-definitions.component'; @@ -225,6 +223,10 @@ import { AppConfigService } from './services/config/app-config.service'; import { AnalyticViewComponent } from './views/stix/analytic-view/analytic-view.component'; import { DetectionStrategyViewComponent } from './views/stix/detection-strategy-view/detection-strategy-view.component'; import { StixListPageComponent } from './views/stix/stix-list-page/stix-list-page.component'; +import { DictionaryPropertyComponent } from './components/stix/dictionary-property/dictionary-property.component'; +import { DictionaryEditComponent } from './components/stix/dictionary-property/dictionary-edit/dictionary-edit.component'; +import { DictionaryViewComponent } from './components/stix/dictionary-property/dictionary-view/dictionary-view.component'; +import { DictionaryDiffComponent } from './components/stix/dictionary-property/dictionary-diff/dictionary-diff.component'; export function initConfig(appConfigService: AppConfigService) { return () => appConfigService.loadAppConfig(); @@ -269,9 +271,6 @@ export function initConfig(appConfigService: AppConfigService) { TlpViewComponent, TlpEditComponent, AttackIDPropertyComponent, - AttackIDEditComponent, - AttackIDViewComponent, - AttackidDiffComponent, StixIDPropertyComponent, ListPropertyComponent, ListEditComponent, @@ -287,6 +286,7 @@ export function initConfig(appConfigService: AppConfigService) { LandingPageComponent, HelpPageComponent, DashboardPageComponent, + DataQualityComponent, OrgSettingsPageComponent, UserAccountsPageComponent, DefaultMarkingDefinitionsComponent, @@ -368,6 +368,10 @@ export function initConfig(appConfigService: AppConfigService) { StixJsonDialogComponent, OutdatedContentWarningComponent, StreamProgressComponent, + DictionaryPropertyComponent, + DictionaryEditComponent, + DictionaryViewComponent, + DictionaryDiffComponent, ], exports: [ MatToolbarModule, diff --git a/src/app/classes/external-references.ts b/src/app/classes/external-references.ts index e4d4ec9cf..cfb1f48f6 100644 --- a/src/app/classes/external-references.ts +++ b/src/app/classes/external-references.ts @@ -5,6 +5,7 @@ import { Serializable, ValidationData } from './serializable'; import { StixObject } from './stix/stix-object'; import { logger } from '../utils/logger'; import { RelatedAsset } from './stix/asset'; +import { xMitreFirstSeenCitationSchema } from '@mitre-attack/attack-data-model'; export class ExternalReferences extends Serializable { private _externalReferences = new Map(); @@ -12,6 +13,7 @@ export class ExternalReferences extends Serializable { private usedReferences: string[] = []; // array to store used references private missingReferences: string[] = []; // array to store missing references private brokenCitations: string[] = []; // array to store broken citations + private invalidCitations: string[] = []; // array to store invalid citations /** * return external references list @@ -208,38 +210,54 @@ export class ExternalReferences extends Serializable { restApiConnector: RestApiConnectorService ): Observable { const reReference = /\(Citation: (.*?)\)/gmu; - const citations = value.match(reReference); const result = new CitationParseResult({ brokenCitations: this.validateBrokenCitations(value, [ /\(Citation:([^ ].*?)\)/gmu, /\(citation:(.*?)\)/gmu, + /\(Citation [^)]+\)/gmu, ]), }); + const apiMap: { [key: string]: Observable } = {}; // Initialize API map + // Extract citations even if the value doesn't pass validation + const citations = value.match(reReference); // Extract citations using regex + // Process citations if (citations) { - // build lookup api map - const api_map = {}; for (const citation of citations) { - // Split to get source name from citation - const sourceName = citation.split('(Citation: ')[1].slice(0, -1); - api_map[sourceName] = this.checkAndAddReference( - sourceName, - restApiConnector - ); + const validateValue = xMitreFirstSeenCitationSchema.safeParse(citation); + if (validateValue.success) { + // Extract source name from citation + const sourceName = citation.split('(Citation: ')[1].slice(0, -1); + // Add API call to the map + apiMap[sourceName] = this.checkAndAddReference( + sourceName, + restApiConnector + ); + } else { + if (citation != '') { + result.invalidCitations.add(citation); + } + } } - // check/add each citation - return forkJoin(api_map).pipe( - map(api_results => { - const citation_results = api_results as any; - for (const key of Object.keys(citation_results)) { - // was the result able to be found/added? - if (citation_results[key]) result.usedCitations.add(key); - else result.missingCitations.add(key); + } + // If there are valid citations to process, use forkJoin + if (Object.keys(apiMap).length > 0) { + return forkJoin(apiMap).pipe( + map(apiResults => { + const citationResults = apiResults as any; + for (const key of Object.keys(citationResults)) { + // Check if the citation was successfully processed + if (citationResults[key]) { + result.usedCitations.add(key); + } else { + result.missingCitations.add(key); + } } return result; }) ); } else { + // If no valid citations, return the result immediately return of(result); } } @@ -497,6 +515,20 @@ export class ExternalReferences extends Serializable { field: 'external_references', //TODO set this to the actual field to improve warnings message: `Citations ${brokenCitations.join(', ')} do not match format (Citation: source name)`, }); + // invalid citations + const invalidCitations = Array.from(citationResult.invalidCitations); + if (invalidCitations.length == 1) + result.errors.push({ + result: 'error', + field: 'external_references', //TODO set this to the actual field to improve warnings + message: `Citation does not match format (Citation: source name) or (Citation: source name)(Citation: source name)`, + }); + else if (invalidCitations.length > 1) + result.errors.push({ + result: 'error', + field: 'external_references', //TODO set this to the actual field to improve warnings + message: `Citations ${invalidCitations.join(', ')} do not match format (Citation: source name) or (Citation: source name)(Citation: source name)`, + }); //missing citations const missingCitations = Array.from(citationResult.missingCitations); @@ -538,11 +570,14 @@ export class CitationParseResult { public missingCitations = new Set(); // list of broken references detected in the field public brokenCitations = new Set(); + // list of invalid references detected in the field + public invalidCitations = new Set(); constructor(initData?: { usedCitations?: Set; missingCitations?: Set; brokenCitations: Set; + invalidCitations?: Set; }) { if (initData && initData.usedCitations) this.usedCitations = initData.usedCitations; @@ -550,6 +585,8 @@ export class CitationParseResult { this.missingCitations = initData.missingCitations; if (initData && initData.brokenCitations) this.brokenCitations = initData.brokenCitations; + if (initData && initData.invalidCitations) + this.invalidCitations = initData.invalidCitations; } /** @@ -569,5 +606,9 @@ export class CitationParseResult { ...this.brokenCitations, ...that.brokenCitations, ]); + this.invalidCitations = new Set([ + ...this.invalidCitations, + ...that.invalidCitations, + ]); } } diff --git a/src/app/classes/serializable.ts b/src/app/classes/serializable.ts index ca25a6390..286bf37c4 100644 --- a/src/app/classes/serializable.ts +++ b/src/app/classes/serializable.ts @@ -1,5 +1,6 @@ import { Observable } from 'rxjs'; import { RestApiConnectorService } from '../services/connectors/rest-api/rest-api-connector.service'; +import { WorkflowState } from '../utils/types'; /** * Objects which are serializable to the REST API implement this class @@ -27,7 +28,8 @@ export abstract class Serializable { */ public abstract validate( restAPIService: RestApiConnectorService, - options?: any + options?: any, + tempWorkflowState?: WorkflowState ): Observable; } diff --git a/src/app/classes/stix/analytic.ts b/src/app/classes/stix/analytic.ts index dcebfe0a5..906009fa3 100644 --- a/src/app/classes/stix/analytic.ts +++ b/src/app/classes/stix/analytic.ts @@ -1,9 +1,9 @@ -import { RelatedRef, StixObject } from './stix-object'; +import { EmbeddedRelationship, StixObject } from './stix-object'; import { logger } from '../../utils/logger'; import { catchError, forkJoin, map, Observable, of, switchMap } from 'rxjs'; import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; import { ValidationData } from '../serializable'; -import { StixType } from 'src/app/utils/types'; +import { StixType, WorkflowState } from 'src/app/utils/types'; export class Analytic extends StixObject { public name = ''; @@ -13,7 +13,7 @@ export class Analytic extends StixObject { public mutableElements: MutableElement[] = []; // NOTE: the following fields will only be populated when this object is fetched with the `includeRefs=true` param - public relatedDetections: RelatedRef[] = []; + public relatedDetections: EmbeddedRelationship[] = []; public readonly supportsAttackID = true; protected get attackIDValidator() { @@ -72,6 +72,10 @@ export class Analytic extends StixObject { ); if (this.mutableElements?.length) rep.stix.x_mitre_mutable_elements = this.mutableElements; + + // Strip properties that are empty strs + lists + rep.stix = this.filterObject(rep.stix); + return rep; } @@ -136,22 +140,23 @@ export class Analytic extends StixObject { } else this.mutableElements = []; } - this.deserializeRelatedRefs(raw); + this.deserializeEmbeddedRelationships(raw); } - public deserializeRelatedRefs(raw: any): void { - if ('related_to' in raw) { - const relatedTo = raw.related_to as any[]; - const relatedRefs: RelatedRef[] = relatedTo.map(ref => ({ - stixId: ref.id, + public deserializeEmbeddedRelationships(raw: any): void { + if (raw.workspace?.embedded_relationships) { + const relatedTo = raw.workspace.embedded_relationships as any[]; + const relatedRefs: EmbeddedRelationship[] = relatedTo.map(ref => ({ + stixId: ref.stix_id, name: ref.name, attackId: ref.attack_id, - type: ref.type as StixType, })); - this.relatedDetections = relatedRefs.filter( - ref => ref.type === 'x-mitre-detection-strategy' - ); + function isDetectionStrategy(o: EmbeddedRelationship) { + return o.stixId.includes('x-mitre-detection-strategy'); + } + + this.relatedDetections = relatedRefs.filter(isDetectionStrategy); } } @@ -173,9 +178,10 @@ export class Analytic extends StixObject { * @returns {Observable} the validation warnings and errors once validation is complete. */ public validate( - restAPIService: RestApiConnectorService + restAPIService: RestApiConnectorService, + tempWorkflowState?: WorkflowState ): Observable { - return this.base_validate(restAPIService).pipe( + return this.base_validate(restAPIService, tempWorkflowState).pipe( switchMap(result => { // validate unique mutable fields if (this.mutableElements.length) { @@ -192,35 +198,6 @@ export class Analytic extends StixObject { seen.add(normalizedField); } } - - // validate unique log source references - if (this.logSourceReferences.length) { - return forkJoin( - this.logSourceReferences.map(ref => - restAPIService.getDataComponent(ref.dataComponentRef).pipe( - catchError(() => of(null)) // fallback if API fails - ) - ) - ).pipe( - map(dataComponents => { - const seen = new Set(); - this.logSourceReferences.forEach((lsr, idx) => { - const key = `${lsr.dataComponentRef}::${lsr.name}::${lsr.channel}`; - if (seen.has(key)) { - const dataComponent = dataComponents[idx]; - result.errors.push({ - field: 'logSourceReferences', - result: 'error', - message: `Duplicate log source reference found: ${dataComponent?.[0].attackID || 'unknown'}`, - }); - } - seen.add(key); - }); - return result; - }) - ); - } - return of(result); }) ); diff --git a/src/app/classes/stix/asset.ts b/src/app/classes/stix/asset.ts index 6409e103e..06f2be97f 100644 --- a/src/app/classes/stix/asset.ts +++ b/src/app/classes/stix/asset.ts @@ -3,6 +3,7 @@ import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/re import { logger } from '../../utils/logger'; import { ValidationData } from '../serializable'; import { StixObject } from './stix-object'; +import { WorkflowState } from 'src/app/utils/types'; export class Asset extends StixObject { public name = ''; @@ -72,6 +73,9 @@ export class Asset extends StixObject { contributors => contributors.map(x => x.trim()) ); + // Strip properties that are empty strs + lists + rep.stix = this.filterObject(rep.stix); + return rep; } @@ -136,9 +140,10 @@ export class Asset extends StixObject { * @returns {Observable} the validation warnings and errors once validation is complete. */ public validate( - restAPIService: RestApiConnectorService + restAPIService: RestApiConnectorService, + tempWorkflowState?: WorkflowState ): Observable { - return this.base_validate(restAPIService); + return this.base_validate(restAPIService, tempWorkflowState); } /** diff --git a/src/app/classes/stix/campaign.ts b/src/app/classes/stix/campaign.ts index c44b825c7..00a107e7c 100644 --- a/src/app/classes/stix/campaign.ts +++ b/src/app/classes/stix/campaign.ts @@ -1,8 +1,9 @@ -import { StixObject } from './stix-object'; -import { logger } from '../../utils/logger'; import { Observable } from 'rxjs'; import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { logger } from '../../utils/logger'; import { ValidationData } from '../serializable'; +import { StixObject } from './stix-object'; +import { WorkflowState } from 'src/app/utils/types'; export class Campaign extends StixObject { public name = ''; @@ -40,13 +41,16 @@ export class Campaign extends StixObject { } rep.stix.name = this.name.trim(); - rep.stix.first_seen = this.first_seen.toISOString(); - rep.stix.last_seen = this.last_seen.toISOString(); + rep.stix.first_seen = this.first_seen?.toISOString(); + rep.stix.last_seen = this.last_seen?.toISOString(); rep.stix.x_mitre_first_seen_citation = this.first_seen_citation.trim(); rep.stix.x_mitre_last_seen_citation = this.last_seen_citation.trim(); rep.stix.aliases = this.aliases.map(x => x.trim()); rep.stix.x_mitre_contributors = this.contributors.map(x => x.trim()); + // Strip properties that are empty strs + lists + rep.stix = this.filterObject(rep.stix); + return rep; } @@ -156,9 +160,10 @@ export class Campaign extends StixObject { * @returns {Observable} the validation warnings and errors once validation is complete. */ public validate( - restAPIService: RestApiConnectorService + restAPIService: RestApiConnectorService, + tempWorkflowState?: WorkflowState ): Observable { - return this.base_validate(restAPIService); + return this.base_validate(restAPIService, tempWorkflowState); } /** diff --git a/src/app/classes/stix/collection.ts b/src/app/classes/stix/collection.ts index f2ed9382c..ed9d5ac39 100644 --- a/src/app/classes/stix/collection.ts +++ b/src/app/classes/stix/collection.ts @@ -19,6 +19,7 @@ import { Tactic, Technique, } from '../stix'; +import { WorkflowState } from 'src/app/utils/types'; /** * auto-generated changelog/report about an import @@ -496,9 +497,10 @@ export class Collection extends StixObject { * @returns {Observable} the validation warnings and errors once validation is complete. */ public validate( - restAPIService: RestApiConnectorService + restAPIService: RestApiConnectorService, + tempWorkflowState?: WorkflowState ): Observable { - return this.base_validate(restAPIService); + return this.base_validate(restAPIService, tempWorkflowState); } /** diff --git a/src/app/classes/stix/data-component.ts b/src/app/classes/stix/data-component.ts index a41196f6e..6bc177822 100644 --- a/src/app/classes/stix/data-component.ts +++ b/src/app/classes/stix/data-component.ts @@ -4,6 +4,7 @@ import { ValidationData } from '../serializable'; import { StixObject } from './stix-object'; import { logger } from '../../utils/logger'; import { DataSource } from './data-source'; +import { WorkflowState } from 'src/app/utils/types'; export class DataComponent extends StixObject { public name = ''; @@ -49,6 +50,9 @@ export class DataComponent extends StixObject { rep.stix.x_mitre_log_sources = this.logSources; } + // Strip properties that are empty strs + lists + rep.stix = this.filterObject(rep.stix); + return rep; } @@ -128,28 +132,10 @@ export class DataComponent extends StixObject { * @returns {Observable} the validation warnings and errors once validation is complete. */ public validate( - restAPIService: RestApiConnectorService + restAPIService: RestApiConnectorService, + tempWorkflowState?: WorkflowState ): Observable { - return this.base_validate(restAPIService).pipe( - map(result => { - if (!this.logSources.length) return result; - - // validate log sources array - const seen = new Set(); - for (const { name, channel } of this.logSources) { - const key = `${name}::${channel}`; - if (seen.has(key)) { - result.errors.push({ - field: 'permutations', - result: 'error', - message: `Duplicate log source found: name="${name}", channel="${channel}"`, - }); - } - seen.add(key); - } - return result; - }) - ); + return this.base_validate(restAPIService, tempWorkflowState); } /** diff --git a/src/app/classes/stix/data-source.ts b/src/app/classes/stix/data-source.ts index ed5535bd8..24a215a96 100644 --- a/src/app/classes/stix/data-source.ts +++ b/src/app/classes/stix/data-source.ts @@ -4,6 +4,7 @@ import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/re import { Observable } from 'rxjs'; import { ValidationData } from '../serializable'; import { DataComponent } from './data-component'; +import { WorkflowState } from 'src/app/utils/types'; export class DataSource extends StixObject { public name = ''; @@ -47,6 +48,9 @@ export class DataSource extends StixObject { rep.stix.x_mitre_contributors = this.contributors.map(x => x.trim()); rep.stix.x_mitre_domains = this.domains; + // Strip properties that are empty strs + lists + rep.stix = this.filterObject(rep.stix); + return rep; } @@ -131,9 +135,10 @@ export class DataSource extends StixObject { * @returns {Observable} the validation warnings and errors once validation is complete. */ public validate( - restAPIService: RestApiConnectorService + restAPIService: RestApiConnectorService, + tempWorkflowState?: WorkflowState ): Observable { - return this.base_validate(restAPIService); + return this.base_validate(restAPIService, tempWorkflowState); } /** diff --git a/src/app/classes/stix/detection-strategy.ts b/src/app/classes/stix/detection-strategy.ts index ef44a1280..a1cfa794a 100644 --- a/src/app/classes/stix/detection-strategy.ts +++ b/src/app/classes/stix/detection-strategy.ts @@ -3,6 +3,7 @@ import { logger } from '../../utils/logger'; import { Observable } from 'rxjs'; import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; import { ValidationData } from '../serializable'; +import { WorkflowState } from 'src/app/utils/types'; export class DetectionStrategy extends StixObject { public name = ''; @@ -37,6 +38,9 @@ export class DetectionStrategy extends StixObject { rep.stix.x_mitre_contributors = this.contributors.map(x => x.trim()); if (this.analytics) rep.stix.x_mitre_analytic_refs = this.analytics; + // Strip properties that are empty strs + lists + rep.stix = this.filterObject(rep.stix); + return rep; } @@ -83,9 +87,10 @@ export class DetectionStrategy extends StixObject { * @returns {Observable} the validation warnings and errors once validation is complete. */ public validate( - restAPIService: RestApiConnectorService + restAPIService: RestApiConnectorService, + tempWorkflowState?: WorkflowState ): Observable { - return this.base_validate(restAPIService); + return this.base_validate(restAPIService, tempWorkflowState); } /** diff --git a/src/app/classes/stix/group.ts b/src/app/classes/stix/group.ts index 541055970..947c56aee 100644 --- a/src/app/classes/stix/group.ts +++ b/src/app/classes/stix/group.ts @@ -3,6 +3,7 @@ import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/re import { Observable } from 'rxjs'; import { ValidationData } from '../serializable'; import { logger } from '../../utils/logger'; +import { WorkflowState } from 'src/app/utils/types'; export class Group extends StixObject { public name = ''; @@ -38,6 +39,9 @@ export class Group extends StixObject { rep.stix.aliases = this.aliases.map(x => x.trim()); rep.stix.x_mitre_contributors = this.contributors.map(x => x.trim()); + // Strip properties that are empty strs + lists + rep.stix = this.filterObject(rep.stix); + return rep; } @@ -95,9 +99,10 @@ export class Group extends StixObject { * @returns {Observable} the validation warnings and errors once validation is complete. */ public validate( - restAPIService: RestApiConnectorService + restAPIService: RestApiConnectorService, + tempWorkflowState?: WorkflowState ): Observable { - return this.base_validate(restAPIService); + return this.base_validate(restAPIService, tempWorkflowState); } /** diff --git a/src/app/classes/stix/identity.ts b/src/app/classes/stix/identity.ts index 4b54131a1..efcff67f0 100644 --- a/src/app/classes/stix/identity.ts +++ b/src/app/classes/stix/identity.ts @@ -1,8 +1,9 @@ import { Observable, of } from 'rxjs'; import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { logger } from '../../utils/logger'; import { ValidationData } from '../serializable'; import { StixObject } from './stix-object'; -import { logger } from '../../utils/logger'; +import { WorkflowState } from 'src/app/utils/types'; export class Identity extends StixObject { public name: string; // identity name @@ -15,6 +16,9 @@ export class Identity extends StixObject { return null; } // identities do not have an ATT&CK ID + // override StixObject excludedFields + protected excludedFields = ['x_mitre_version']; + constructor(sdo?: any) { super(sdo, 'identity'); if (sdo) { @@ -35,6 +39,9 @@ export class Identity extends StixObject { if (this.roles) rep.stix.roles = this.roles; if (this.contact) rep.stix.contact_information = this.contact; + // Strip properties that are empty strs + lists + rep.stix = this.filterObject(rep.stix); + return rep; } @@ -98,9 +105,10 @@ export class Identity extends StixObject { * @returns {Observable} the validation warnings and errors once validation is complete. */ public validate( - restAPIService: RestApiConnectorService + restAPIService: RestApiConnectorService, + tempWorkflowState?: WorkflowState ): Observable { - return this.base_validate(restAPIService); + return this.base_validate(restAPIService, tempWorkflowState); } /** diff --git a/src/app/classes/stix/marking-definition.ts b/src/app/classes/stix/marking-definition.ts index a2b23e28c..458e7e744 100644 --- a/src/app/classes/stix/marking-definition.ts +++ b/src/app/classes/stix/marking-definition.ts @@ -4,6 +4,7 @@ import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/re import { Observable, of } from 'rxjs'; import { ValidationData } from '../serializable'; import { logger } from '../../utils/logger'; +import { WorkflowState } from 'src/app/utils/types'; export class MarkingDefinition extends StixObject { public name = ''; @@ -15,6 +16,9 @@ export class MarkingDefinition extends StixObject { return null; } //marking-defs do not have ATT&CK IDs + // override StixObject excludedFields + protected excludedFields = ['x_mitre_version', 'x_mitre_deprecated']; + constructor(sdo?: any) { super(sdo, 'marking-definition'); if (sdo) { @@ -35,6 +39,9 @@ export class MarkingDefinition extends StixObject { rep.stix.definition = {}; rep.stix.definition[this.definition_type] = this.definition_string; + // Strip properties that are empty strs + lists + rep.stix = this.filterObject(rep.stix); + return rep; } @@ -96,9 +103,10 @@ export class MarkingDefinition extends StixObject { * @returns {Observable} the validation warnings and errors once validation is complete. */ public validate( - restAPIService: RestApiConnectorService + restAPIService: RestApiConnectorService, + tempWorkflowState?: WorkflowState ): Observable { - return this.base_validate(restAPIService).pipe( + return this.base_validate(restAPIService, tempWorkflowState).pipe( map(result => { // presence of statement if (!this.definition_string) { diff --git a/src/app/classes/stix/matrix.ts b/src/app/classes/stix/matrix.ts index 00831abfc..fde7e2e99 100644 --- a/src/app/classes/stix/matrix.ts +++ b/src/app/classes/stix/matrix.ts @@ -4,6 +4,7 @@ import { ValidationData } from '../serializable'; import { StixObject } from './stix-object'; import { logger } from '../../utils/logger'; import { Tactic } from './tactic'; +import { WorkflowState } from 'src/app/utils/types'; export class Matrix extends StixObject { public name = ''; @@ -40,6 +41,9 @@ export class Matrix extends StixObject { rep.stix.name = this.name.trim(); rep.stix.tactic_refs = this.tactic_refs; + // Strip properties that are empty strs + lists + rep.stix = this.filterObject(rep.stix); + return rep; } @@ -85,10 +89,11 @@ export class Matrix extends StixObject { * @returns {Observable} the validation warnings and errors once validation is complete. */ public validate( - restAPIService: RestApiConnectorService + restAPIService: RestApiConnectorService, + tempWorkflowState?: WorkflowState ): Observable { // TODO verify all tactics exist - return this.base_validate(restAPIService); + return this.base_validate(restAPIService, tempWorkflowState); } /** diff --git a/src/app/classes/stix/mitigation.ts b/src/app/classes/stix/mitigation.ts index bf1f72ae4..e2b0e942c 100644 --- a/src/app/classes/stix/mitigation.ts +++ b/src/app/classes/stix/mitigation.ts @@ -3,6 +3,7 @@ import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/re import { ValidationData } from '../serializable'; import { StixObject } from './stix-object'; import { logger } from '../../utils/logger'; +import { WorkflowState } from 'src/app/utils/types'; export class Mitigation extends StixObject { public name = ''; @@ -46,6 +47,9 @@ export class Mitigation extends StixObject { rep.stix.labels = this.securityControls; } + // Strip properties that are empty strs + lists + rep.stix = this.filterObject(rep.stix); + return rep; } @@ -89,9 +93,10 @@ export class Mitigation extends StixObject { * @returns {Observable} the validation warnings and errors once validation is complete. */ public validate( - restAPIService: RestApiConnectorService + restAPIService: RestApiConnectorService, + tempWorkflowState?: WorkflowState ): Observable { - return this.base_validate(restAPIService); + return this.base_validate(restAPIService, tempWorkflowState); } /** diff --git a/src/app/classes/stix/note.ts b/src/app/classes/stix/note.ts index 4ff26279c..bbf0e80e5 100644 --- a/src/app/classes/stix/note.ts +++ b/src/app/classes/stix/note.ts @@ -3,6 +3,7 @@ import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/re import { ValidationData } from '../serializable'; import { StixObject } from './stix-object'; import { logger } from '../../utils/logger'; +import { WorkflowState } from 'src/app/utils/types'; export class Note extends StixObject { public title = ''; @@ -86,9 +87,10 @@ export class Note extends StixObject { * @returns {Observable} the validation warnings and errors once validation is complete. */ public validate( - restAPIService: RestApiConnectorService + restAPIService: RestApiConnectorService, + tempWorkflowState?: WorkflowState ): Observable { - return this.base_validate(restAPIService); + return this.base_validate(restAPIService, tempWorkflowState); } /** diff --git a/src/app/classes/stix/relationship.ts b/src/app/classes/stix/relationship.ts index 5d4fa6053..b05c0a071 100644 --- a/src/app/classes/stix/relationship.ts +++ b/src/app/classes/stix/relationship.ts @@ -1,22 +1,23 @@ import { Observable, of } from 'rxjs'; import { map, switchMap } from 'rxjs/operators'; -import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; -import { ValidationData } from '../serializable'; -import { StixObject } from './stix-object'; -import { logger } from '../../utils/logger'; import { Asset, Campaign, DataComponent, DataSource, + DetectionStrategy, Group, Matrix, Mitigation, Software, Tactic, Technique, - DetectionStrategy, } from 'src/app/classes/stix'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { logger } from '../../utils/logger'; +import { ValidationData } from '../serializable'; +import { StixObject } from './stix-object'; +import { WorkflowState } from 'src/app/utils/types'; export class Relationship extends StixObject { public source_ref = ''; @@ -44,6 +45,9 @@ export class Relationship extends StixObject { return null; } // relationships have no ATT&CK ID + // override StixObject excludedFields + protected excludedFields = ['x_mitre_version']; + /** * Creates and returns the deserialized object * @param type the stix type of the object @@ -379,6 +383,9 @@ export class Relationship extends StixObject { rep.stix.source_ref = this.source_ref; rep.stix.target_ref = this.target_ref; + // Strip properties that are empty strs + lists + rep.stix = this.filterObject(rep.stix); + return rep; } @@ -478,9 +485,10 @@ export class Relationship extends StixObject { * @returns {Observable} the validation warnings and errors once validation is complete. */ public validate( - restAPIService: RestApiConnectorService + restAPIService: RestApiConnectorService, + tempWorkflowState?: WorkflowState ): Observable { - return this.base_validate(restAPIService).pipe( + return this.base_validate(restAPIService, tempWorkflowState).pipe( map(result => { // presence of source-ref if (!this.source_ref) { @@ -615,7 +623,7 @@ export class Relationship extends StixObject { ): Observable { if (!this.workflow) { // Initialize the workflow object if it doesn't exist - this.workflow = { state: '' }; + this.workflow = { state: 'work-in-progress' }; } this.workflow.state = 'work-in-progress'; const postObservable = restAPIService.postRelationship(this); @@ -686,7 +694,7 @@ export class Relationship extends StixObject { // Check if the workflow object exists if (!object.workflow) { // Initialize the workflow object if it doesn't exist - object.workflow = { state: '' }; + object.workflow = { state: 'work-in-progress' }; } object.workflow.state = 'work-in-progress'; object.update(restAPIService).subscribe({ diff --git a/src/app/classes/stix/software.ts b/src/app/classes/stix/software.ts index c4b4539ab..ba331fdd9 100644 --- a/src/app/classes/stix/software.ts +++ b/src/app/classes/stix/software.ts @@ -3,6 +3,7 @@ import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/re import { ValidationData } from '../serializable'; import { StixObject } from './stix-object'; import { logger } from '../../utils/logger'; +import { WorkflowState } from 'src/app/utils/types'; type type_software = 'malware' | 'tool'; export class Software extends StixObject { @@ -48,6 +49,9 @@ export class Software extends StixObject { rep.stix.x_mitre_contributors = this.contributors.map(x => x.trim()); if (this.type == 'malware') rep.stix.is_family = true; // add is_family to malware type SDOs + // Strip properties that are empty strs + lists + rep.stix = this.filterObject(rep.stix); + return rep; } @@ -137,9 +141,10 @@ export class Software extends StixObject { * @returns {Observable} the validation warnings and errors once validation is complete. */ public validate( - restAPIService: RestApiConnectorService + restAPIService: RestApiConnectorService, + tempWorkflowState?: WorkflowState ): Observable { - return this.base_validate(restAPIService); + return this.base_validate(restAPIService, tempWorkflowState); } /** diff --git a/src/app/classes/stix/stix-object.ts b/src/app/classes/stix/stix-object.ts index 6e96e7dbb..96e3831a5 100644 --- a/src/app/classes/stix/stix-object.ts +++ b/src/app/classes/stix/stix-object.ts @@ -1,3 +1,7 @@ +import { + createAttackIdSchema, + StixTypesWithAttackIds, +} from '@mitre-attack/attack-data-model'; import { forkJoin, Observable, of } from 'rxjs'; import { map, switchMap } from 'rxjs/operators'; import { @@ -8,7 +12,7 @@ import { AttackTypeToRoute, StixTypeToAttackType, } from 'src/app/utils/type-mappings'; -import { StixType } from 'src/app/utils/types'; +import { StixType, WorkflowState } from 'src/app/utils/types'; import { v4 as uuid } from 'uuid'; import { logger } from '../../utils/logger'; import { ExternalReferences } from '../external-references'; @@ -18,8 +22,7 @@ import { VersionNumber } from '../version-number'; export type workflowStates = | 'work-in-progress' | 'awaiting-review' - | 'reviewed' - | ''; + | 'reviewed'; export abstract class StixObject extends Serializable { public stixID: string; // STIX ID @@ -37,11 +40,15 @@ export abstract class StixObject extends Serializable { public object_marking_refs: string[] = []; //list of embedded relationships to marking_defs public abstract readonly supportsAttackID: boolean; // boolean to determine if object supports ATT&CK IDs + public tempWorkflowState: WorkflowState; protected abstract get attackIDValidator(): { regex: string; // regex to validate the ID format: string; // format to display to user }; + // fields to omit. By default, do not omit any fields + protected excludedFields: string[] = []; + protected buildAttackExternalReference(): object | null { if (this.attackID && AttackTypeToRoute[this.attackType]) { return { @@ -55,21 +62,6 @@ export abstract class StixObject extends Serializable { private defaultMarkingDefinitionsLoaded = false; // avoid overloading of default marking definitions - public get routes(): any[] { - // route to view the object - return [ - { - label: 'view', - route: '', - }, - { - label: 'edit', - route: '', - query: { editing: true }, - }, - ]; - } - public created: Date; // object created date public modified: Date; // object modified date public version: VersionNumber; // version number of the object @@ -123,25 +115,29 @@ export abstract class StixObject extends Serializable { serialized_external_references.unshift(attackExtRef); } - const stix: any = { + const stix = this.filterObject({ type: this.type, id: this.stixID, created: this.created ? this.created.toISOString() : new Date().toISOString(), - x_mitre_version: this.version.toString(), - external_references: serialized_external_references, + modified: + this.type !== 'marking-definition' + ? new Date().toISOString() + : undefined, + x_mitre_version: this.version?.toString(), x_mitre_deprecated: this.deprecated, revoked: this.revoked, - object_marking_refs: this.object_marking_refs, spec_version: '2.1', - }; - if (this.description) stix.description = this.description; - // Add modified date if type is not marking-definition - if (this.type != 'marking-definition') - stix['modified'] = new Date().toISOString(); - if (this.created_by_ref) stix.created_by_ref = this.created_by_ref; - // do not set modified by ref since we don't know who we are, but the REST API knows + description: this.description, + created_by_ref: this.created_by_ref, + object_marking_refs: this.object_marking_refs, + external_references: serialized_external_references, + }); + + for (const field of this.excludedFields) { + delete stix[field]; + } return { workspace: { @@ -383,11 +379,14 @@ export abstract class StixObject extends Serializable { * @returns true if the ATT&CK ID is valid, false otherwise */ public isValidAttackId(): boolean { - const idRegex = new RegExp( - '^([A-Z]+-)?' + this.attackIDValidator.regex + '$' - ); - const attackIDValid = idRegex.test(this.attackID); - return attackIDValid; + if (this.type in StixTypeToAttackType) { + const attackIDSchema = createAttackIdSchema( + this.type as StixTypesWithAttackIds + ); + const attackIDValid = attackIDSchema.safeParse(this.attackID); + return attackIDValid.success; + } + return false; } /** @@ -397,22 +396,55 @@ export abstract class StixObject extends Serializable { * @returns {Observable} the validation warnings and errors once validation is complete. */ public base_validate( - restAPIService: RestApiConnectorService + restAPIService: RestApiConnectorService, + tempWorkflowState? ): Observable { - const validation = new ValidationData(); - - // test version number format - if (!this.version.valid()) { - validation.errors.push({ - result: 'error', - field: 'version', - message: 'version number is not formatted properly', - }); + if (tempWorkflowState) { + this.workflow = { state: tempWorkflowState }; } // check any asynchronous validators - return of(validation).pipe( - // check if the name is unique if it has a name - switchMap(result => { + const result = new ValidationData(); + // Throws user friendly error when name is missing in the stix object + // Need to exclude analytics from this check because user cannot set names + // for analytics and those are automatically generated + if (this.hasOwnProperty('name') && this.type != 'x-mitre-analytic') { + if ('name' in this) { + const name = this.name; + if (typeof name !== 'string' || name.trim() === '') { + result.errors.push({ + result: 'error', + field: 'name', + message: 'name is required', + }); + return of(result); + } + } + } + const validator = restAPIService.validateStixObject(); + + return validator(this).pipe( + switchMap(validatorResult => { + // Process validation errors from API (backend now handles error-to-warning conversion) + (validatorResult.errors || []).forEach((err: any) => { + const errorMessage = `${err.path.join('.')}: ${err.message}`; + result.errors.push({ + result: 'error', + field: 'temp', + message: errorMessage, + }); + }); + + // Process validation warnings from API + (validatorResult.warnings || []).forEach((warning: any) => { + const warningMessage = + warning.message || `${warning.path.join('.')}: ${warning.message}`; + result.warnings.push({ + result: 'warning', + field: warning.path[warning.path.length - 1] || 'temp', + message: warningMessage, + }); + }); + // check if the name is unique if it has a name //do not check name or attackID for relationships or marking definitions if ( this.attackType == 'relationship' || @@ -456,13 +488,7 @@ export abstract class StixObject extends Serializable { map(objects => { // check name if (this.hasOwnProperty('name')) { - if (this['name'] == '') { - result.errors.push({ - result: 'error', - field: 'name', - message: 'object has no name', - }); - } else if ( + if ( objects.data.some( x => x['name'].toLowerCase() == this['name'].toLowerCase() && @@ -482,98 +508,6 @@ export abstract class StixObject extends Serializable { }); } } - // check ATT&CK ID, ignoring collections and matrices - if ( - this.attackType !== 'matrix' && - this.hasOwnProperty('supportsAttackID') && - this.supportsAttackID - ) { - if (this.attackID == '') { - if (this.attackType === 'analytic') { - result.errors.push({ - result: 'error', - field: 'attackID', - message: 'object does not have ATT&CK ID', - }); - } else { - result.warnings.push({ - result: 'warning', - field: 'attackID', - message: 'object does not have ATT&CK ID', - }); - } - } else { - if ( - objects.data.some( - x => x.attackID == this.attackID && x.stixID != this.stixID - ) - ) { - result.errors.push({ - result: 'error', - field: 'attackID', - message: 'ATT&CK ID is not unique', - }); - } else { - result.successes.push({ - result: 'success', - field: 'attackID', - message: 'ATT&CK ID is unique', - }); - } - if (!this.isValidAttackId()) { - result.errors.push({ - result: 'error', - field: 'attackID', - message: `ATT&CK ID does not match the format ${this.attackIDValidator.format}`, - }); - } - } - } - // check required first/last seen fields for campaigns - if (this.attackType == 'campaign') { - if ( - !this.hasOwnProperty('first_seen') || - this['first_seen'] == null - ) { - result.errors.push({ - result: 'error', - field: 'first_seen', - message: 'object does not have a first seen date', - }); - } - if ( - !this.hasOwnProperty('first_seen_citation') || - this['first_seen_citation'] == '' - ) { - result.errors.push({ - result: 'error', - field: 'first_seen_citation', - message: - 'object is missing a citation for the first seen date', - }); - } - if ( - !this.hasOwnProperty('last_seen') || - this['last_seen'] == null - ) { - result.errors.push({ - result: 'error', - field: 'last_seen', - message: 'object does not have a last seen date', - }); - } - if ( - !this.hasOwnProperty('last_seen_citation') || - this['last_seen_citation'] == '' - ) { - result.errors.push({ - result: 'error', - field: 'last_seen_citation', - message: - 'object is missing a citation for the last seen date', - }); - } - } return result; }) ); @@ -586,8 +520,6 @@ export abstract class StixObject extends Serializable { refs_fields.push('aliases'); if (this.attackType == 'asset') refs_fields.push('relatedAssets'); if (this.attackType == 'technique') refs_fields.push('detection'); - if (this.attackType == 'campaign') - refs_fields.push('first_seen_citation', 'last_seen_citation'); return this.external_references .validate(restAPIService, { object: this, fields: refs_fields }) @@ -750,6 +682,48 @@ export abstract class StixObject extends Serializable { return result; } + /** + * Checks if the provided field has a valid value. + * Returns undefined or the value of the field if it is valid + * @param {*} field - The value to validate. + * @returns {value} - Value if the field has a valid value, undefined otherwise. + */ + public clean(value) { + if (value == null) return undefined; // null or undefined + if (typeof value === 'string' && value.trim() === '') return undefined; + if (typeof value === 'number' && Number.isNaN(value)) return undefined; + if (Array.isArray(value)) { + if (value.length === 0) { + return undefined; + } else { + const arr = value.map(v => this.clean(v)).filter(v => v !== undefined); + return arr.length ? arr : undefined; + } + } + + if (typeof value === 'object') { + const obj = this.filterObject(value); + return Object.keys(obj).length ? obj : undefined; + } + + return value; + } + + /** + * Filters the properties of an object, returning a new object containing only + * those entries whose values pass the validity check. + * @param {Object} obj - The object to filter. + * @returns {Object} - A new object with only the valid entries. + */ + public filterObject(obj) { + const out = {}; + for (const [key, value] of Object.entries(obj)) { + const cleaned = this.clean(value); + if (cleaned !== undefined) out[key] = cleaned; + } + return out; + } + /** * Check if the given array is a list of strings * @param arr the array to check @@ -843,169 +817,6 @@ export abstract class StixObject extends Serializable { }, }); } - - public generateAttackId( - apiService: RestApiConnectorService, - existingPrefix?: string - ): Observable { - this.attackID = '(generating ID)'; - return apiService.getOrganizationNamespace().pipe( - switchMap(namespace => { - const accessor = this.getApiAccessor( - apiService, - this.attackType, - true, - true - ); - if (!accessor) return of('(unsupported attack type)'); - - const typePrefix = this.getAttackIdPrefix(); // ex: "TA" for tactics - // org prefix (ex: "ORG"), use existing prefix if defined - const orgPrefix = existingPrefix - ? existingPrefix - : (namespace.prefix ?? ''); - // family prefix: orgPrefix + typePrefix (ex: "ORG-TA" for tactics) - const familyPrefix = orgPrefix - ? orgPrefix + '-' + typePrefix - : typePrefix; - - return accessor.pipe( - switchMap(objects => { - if ('is_subtechnique' in this && this['is_subtechnique']) { - return this.getNextSubtechniqueAttackId( - apiService, - familyPrefix, - typePrefix - ); - } else { - return this.getNextObjectAttackId( - objects, - familyPrefix, - typePrefix, - namespace.range_start - ); - } - }), - map(generatedId => this.formatWithPrefix(generatedId, orgPrefix)) - ); - }) - ); - } - - private getApiAccessor( - apiService: RestApiConnectorService, - attackType: string, - includeDeprecated?: boolean, - includeRevoked?: boolean - ): Observable> { - const options = { - includeDeprecated: includeDeprecated ?? false, - includeRevoked: includeRevoked ?? false, - }; - if (attackType == 'group') return apiService.getAllGroups(options); - else if (attackType == 'campaign') - return apiService.getAllCampaigns(options); - else if (attackType == 'mitigation') - return apiService.getAllMitigations(options); - else if (attackType == 'software') - return apiService.getAllSoftware(options); - else if (attackType == 'tactic') return apiService.getAllTactics(options); - else if (attackType == 'technique') - return apiService.getAllTechniques(options); - else if (attackType == 'data-source') - return apiService.getAllDataSources(options); - else if (attackType == 'data-component') - return apiService.getAllDataComponents(options); - else if (attackType == 'asset') return apiService.getAllAssets(options); - else if (attackType == 'matrix') return apiService.getAllMatrices(options); - else if (attackType == 'detection-strategy') - return apiService.getAllDetectionStrategies(options); - else if (attackType == 'analytic') - return apiService.getAllAnalytics(options); - else return null; - } - - private getAttackIdPrefix(): string { - return this.attackIDValidator.format.includes('#') - ? this.attackIDValidator.format.split('#')[0] - : ''; - } - - private getNextSubtechniqueAttackId( - apiService: RestApiConnectorService, - orgPrefix: string, - typePrefix: string - ): Observable { - if (!('parentTechnique' in this && this['parentTechnique'])) { - return of('(parent technique missing)'); - } - - // get 4-digit ID of parent technique - const parent = this['parentTechnique'] as StixObject; - const found = parent.attackID.match(/[0-9]{4}/g); - if (!found?.length) return of('(invalid parent id)'); - orgPrefix += found[0]; - - return apiService.getTechnique(parent.stixID, null, 'latest', true).pipe( - map(technique => { - const children = technique[0]?.subTechniques ?? []; - let count = 1; - - if (children.length > 0) { - const childIds = children - .filter(obj => obj.attackID.startsWith(orgPrefix)) - .map(obj => obj.attackID.match(/[^.]([0-9]*)$/g)?.[0]) - .filter(Boolean) - .map(Number); - - // get next available subtechnique number - if (childIds.length > 0) { - count = Math.max(...childIds) + 1; - } - } - - // construct new id (e.g. T1234.001) - return `${typePrefix}${found[0]}.${count.toString().padStart(3, '0')}`; - }) - ); - } - - private getNextObjectAttackId( - objects: Paginated, - orgPrefix: string, - typePrefix: string, - rangeStart: string - ): Observable { - // get ids of existing objects that have the same prefix - const currIds = objects.data.reduce((ids, obj) => { - if (obj.attackID.startsWith(orgPrefix)) { - // remove non-digits and decimals - ids.push(obj.attackID.replace(orgPrefix, '').replace(/[.](\d{3})/, '')); - } - return ids; - }, [] as string[]); - - // get next available ID from existing IDs - const next = currIds.length > 0 ? Number(currIds.sort().pop()) + 1 : 1; - - let newId = next; - if (this.firstInitialized && rangeStart) { - // if creating new & range start is defined, use range start if larger than next available ID - newId = +rangeStart > next ? +rangeStart : next; - } - - // construct new id (e.g. G0999) - return of(typePrefix + newId.toString().padStart(4, '0')); - } - - public formatWithPrefix(attackId: string, orgPrefix: string): string { - const prefix = orgPrefix ? orgPrefix + '-' : ''; - const withPrefix = attackId.startsWith(prefix) - ? attackId - : prefix + attackId; - // matrix IDs are case sensitive, all others uppercase - return this.attackType === 'matrix' ? withPrefix : withPrefix.toUpperCase(); - } } /** @@ -1035,9 +846,8 @@ export class LinkByIdParseResult { } } -export interface RelatedRef { +export interface EmbeddedRelationship { stixId: string; name: string; attackId: string; - type: StixType; } diff --git a/src/app/classes/stix/tactic.ts b/src/app/classes/stix/tactic.ts index 77a0f623d..1209d4e4e 100644 --- a/src/app/classes/stix/tactic.ts +++ b/src/app/classes/stix/tactic.ts @@ -4,6 +4,7 @@ import { ValidationData } from '../serializable'; import { StixObject } from './stix-object'; import { logger } from '../../utils/logger'; import { Technique } from './technique'; +import { WorkflowState } from 'src/app/utils/types'; export class Tactic extends StixObject { public name = ''; @@ -47,6 +48,10 @@ export class Tactic extends StixObject { rep.stix.x_mitre_domains = this.domains; rep.stix.x_mitre_shortname = this.shortname; rep.stix.x_mitre_contributors = this.contributors.map(x => x.trim()); + rep.stix = this.filterObject(rep.stix); + + // Strip properties that are empty strs + lists + rep.stix = this.filterObject(rep.stix); return rep; } @@ -99,9 +104,10 @@ export class Tactic extends StixObject { * @returns {Observable} the validation warnings and errors once validation is complete. */ public validate( - restAPIService: RestApiConnectorService + restAPIService: RestApiConnectorService, + tempWorkflowState?: WorkflowState ): Observable { - return this.base_validate(restAPIService); + return this.base_validate(restAPIService, tempWorkflowState); } /** diff --git a/src/app/classes/stix/technique.ts b/src/app/classes/stix/technique.ts index 840bdcd72..167bf68ab 100644 --- a/src/app/classes/stix/technique.ts +++ b/src/app/classes/stix/technique.ts @@ -1,10 +1,11 @@ import { forkJoin, Observable, of } from 'rxjs'; import { concatMap, map, shareReplay, switchMap } from 'rxjs/operators'; import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; -import { ValidationData } from '../serializable'; -import { StixObject } from './stix-object'; import { logger } from '../../utils/logger'; +import { ValidationData } from '../serializable'; import { Relationship } from './relationship'; +import { StixObject } from './stix-object'; +import { WorkflowState } from 'src/app/utils/types'; export class Technique extends StixObject { public name = ''; @@ -243,6 +244,10 @@ export class Technique extends StixObject { } } } + + // Strip properties that are empty strs + lists + rep.stix = this.filterObject(rep.stix); + return rep; } @@ -447,9 +452,10 @@ export class Technique extends StixObject { * @returns {Observable} the validation warnings and errors once validation is complete. */ public validate( - restAPIService: RestApiConnectorService + restAPIService: RestApiConnectorService, + tempWorkflowState?: WorkflowState ): Observable { - return this.base_validate(restAPIService).pipe( + return this.base_validate(restAPIService, tempWorkflowState).pipe( map(result => { // validate technique has at least one tactic if (this.attackID && this.tactics.length == 0) { diff --git a/src/app/classes/version-number.ts b/src/app/classes/version-number.ts index 35e73b4fb..b5affe23c 100644 --- a/src/app/classes/version-number.ts +++ b/src/app/classes/version-number.ts @@ -1,4 +1,4 @@ -import { ValidatorFn, AbstractControl } from '@angular/forms'; +import { AbstractControl, ValidatorFn } from '@angular/forms'; import { logger } from '../utils/logger'; export class VersionNumber { @@ -115,14 +115,6 @@ export class VersionNumber { } return timesIncremented > 1; } - - /** - * Is this version number formatted correctly? - * @returns {boolean} true if valid - */ - public valid(): boolean { - return /^(\d+\.)*\d+$/.test(this.toString()); - } } /** diff --git a/src/app/components/add-dialog/add-dialog.component.spec.ts b/src/app/components/add-dialog/add-dialog.component.spec.ts index 051b783dc..781861b8e 100644 --- a/src/app/components/add-dialog/add-dialog.component.spec.ts +++ b/src/app/components/add-dialog/add-dialog.component.spec.ts @@ -1,4 +1,7 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { SelectionModel } from '@angular/cdk/collections'; import { AddDialogComponent } from './add-dialog.component'; @@ -9,6 +12,17 @@ describe('AddDialogComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [AddDialogComponent], + providers: [ + { provide: MatDialogRef, useValue: {} }, + { + provide: MAT_DIALOG_DATA, + useValue: { + type: 'test', + select: new SelectionModel(true, []), + }, + }, + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); diff --git a/src/app/components/add-relationship-button/add-relationship-button.component.spec.ts b/src/app/components/add-relationship-button/add-relationship-button.component.spec.ts index e0dd52d8d..9a41fbaf2 100644 --- a/src/app/components/add-relationship-button/add-relationship-button.component.spec.ts +++ b/src/app/components/add-relationship-button/add-relationship-button.component.spec.ts @@ -1,20 +1,35 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { AddRelationshipButtonComponent } from './add-relationship-button.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { createMockRestApiConnector } from 'src/app/testing/mocks/rest-api-connector.mock'; describe('AddRelationshipButtonComponent', () => { let component: AddRelationshipButtonComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({}); + await TestBed.configureTestingModule({ declarations: [AddRelationshipButtonComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + provideHttpClient(), + ], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(AddRelationshipButtonComponent); component = fixture.componentInstance; + component.config = { + label: 'test label', + relationship_type: 'test-relationship', + }; fixture.detectChanges(); }); diff --git a/src/app/components/breadcrumb/breadcrumb.component.spec.ts b/src/app/components/breadcrumb/breadcrumb.component.spec.ts index bdb77d03c..b927e5a87 100644 --- a/src/app/components/breadcrumb/breadcrumb.component.spec.ts +++ b/src/app/components/breadcrumb/breadcrumb.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { BreadcrumbComponent } from './breadcrumb.component'; @@ -9,6 +10,7 @@ describe('BreadcrumbComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [BreadcrumbComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); fixture = TestBed.createComponent(BreadcrumbComponent); diff --git a/src/app/components/collection-import-summary/collection-import-summary.component.spec.ts b/src/app/components/collection-import-summary/collection-import-summary.component.spec.ts index 01e46214d..077de4638 100644 --- a/src/app/components/collection-import-summary/collection-import-summary.component.spec.ts +++ b/src/app/components/collection-import-summary/collection-import-summary.component.spec.ts @@ -1,4 +1,7 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatExpansionModule } from '@angular/material/expansion'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { CollectionImportSummaryComponent } from './collection-import-summary.component'; @@ -9,12 +12,31 @@ describe('CollectionImportSummaryComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [CollectionImportSummaryComponent], + imports: [MatExpansionModule, NoopAnimationsModule], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(CollectionImportSummaryComponent); component = fixture.componentInstance; + component.config = { + object_import_categories: { + technique: {} as any, + tactic: {} as any, + campaign: {} as any, + software: {} as any, + relationship: {} as any, + mitigation: {} as any, + matrix: {} as any, + group: {} as any, + data_source: {} as any, + data_component: {} as any, + asset: {} as any, + analytic: {} as any, + detection_strategy: {} as any, + }, + }; fixture.detectChanges(); }); diff --git a/src/app/components/collection-update-dialog/collection-update-dialog.component.spec.ts b/src/app/components/collection-update-dialog/collection-update-dialog.component.spec.ts index 505896ab2..aa6f08c73 100644 --- a/src/app/components/collection-update-dialog/collection-update-dialog.component.spec.ts +++ b/src/app/components/collection-update-dialog/collection-update-dialog.component.spec.ts @@ -1,5 +1,12 @@ +import { provideHttpClient } from '@angular/common/http'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; - +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createAsyncObservable, + createMockRestApiConnector, +} from 'src/app/testing/mocks/rest-api-connector.mock'; import { CollectionUpdateDialogComponent } from './collection-update-dialog.component'; describe('CollectionUpdateDialogComponent', () => { @@ -7,8 +14,26 @@ describe('CollectionUpdateDialogComponent', () => { let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getTacticsRelatedToTechnique: () => createAsyncObservable([]), + }); + await TestBed.configureTestingModule({ declarations: [CollectionUpdateDialogComponent], + providers: [ + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + { provide: MatDialogRef, useValue: {} }, + { + provide: MAT_DIALOG_DATA, + useValue: { + collectionChanges: { + technique: { flatten: () => [] }, + }, + }, + }, + provideHttpClient(), + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); diff --git a/src/app/components/confirmation-dialog/confirmation-dialog.component.spec.ts b/src/app/components/confirmation-dialog/confirmation-dialog.component.spec.ts index c25f48869..d52eb7b55 100644 --- a/src/app/components/confirmation-dialog/confirmation-dialog.component.spec.ts +++ b/src/app/components/confirmation-dialog/confirmation-dialog.component.spec.ts @@ -1,4 +1,6 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { ConfirmationDialogComponent } from './confirmation-dialog.component'; @@ -9,6 +11,16 @@ describe('ConfirmationDialogComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ConfirmationDialogComponent], + providers: [ + { provide: MatDialogRef, useValue: {} }, + { + provide: MAT_DIALOG_DATA, + useValue: { + message: 'Test message', + }, + }, + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); diff --git a/src/app/components/contributor-edit-dialog/contributor-edit-dialog.component.spec.ts b/src/app/components/contributor-edit-dialog/contributor-edit-dialog.component.spec.ts index cf18701bf..7d587fff4 100644 --- a/src/app/components/contributor-edit-dialog/contributor-edit-dialog.component.spec.ts +++ b/src/app/components/contributor-edit-dialog/contributor-edit-dialog.component.spec.ts @@ -1,14 +1,28 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { provideHttpClient } from '@angular/common/http'; import { ContributorEditDialogComponent } from './contributor-edit-dialog.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { createMockRestApiConnector } from 'src/app/testing/mocks/rest-api-connector.mock'; describe('ContributorEditDialogComponent', () => { let component: ContributorEditDialogComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({}); + await TestBed.configureTestingModule({ declarations: [ContributorEditDialogComponent], + providers: [ + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + { provide: MatDialogRef, useValue: {} }, + { provide: MAT_DIALOG_DATA, useValue: {} }, + provideHttpClient(), + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); fixture = TestBed.createComponent(ContributorEditDialogComponent); diff --git a/src/app/components/create-new-dialog/create-new-dialog.component.spec.ts b/src/app/components/create-new-dialog/create-new-dialog.component.spec.ts index 87fcd909b..ba1cc1b95 100644 --- a/src/app/components/create-new-dialog/create-new-dialog.component.spec.ts +++ b/src/app/components/create-new-dialog/create-new-dialog.component.spec.ts @@ -1,4 +1,6 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { CreateNewDialogComponent } from './create-new-dialog.component'; @@ -9,6 +11,19 @@ describe('CreateNewDialogComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [CreateNewDialogComponent], + providers: [ + { provide: MatDialogRef, useValue: {} }, + { + provide: MAT_DIALOG_DATA, + useValue: { + config: { + objectName: 'test', + formObjects: [], + }, + }, + }, + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); diff --git a/src/app/components/delete-dialog/delete-dialog.component.spec.ts b/src/app/components/delete-dialog/delete-dialog.component.spec.ts index 65c84a935..7bfb56929 100644 --- a/src/app/components/delete-dialog/delete-dialog.component.spec.ts +++ b/src/app/components/delete-dialog/delete-dialog.component.spec.ts @@ -1,4 +1,6 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { DeleteDialogComponent } from './delete-dialog.component'; @@ -9,6 +11,14 @@ describe('DeleteDialogComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [DeleteDialogComponent], + providers: [ + { provide: MatDialogRef, useValue: {} }, + { + provide: MAT_DIALOG_DATA, + useValue: { objectType: 'test', objectName: 'test' }, + }, + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); diff --git a/src/app/components/empty-list-marker/empty-list-marker.component.spec.ts b/src/app/components/empty-list-marker/empty-list-marker.component.spec.ts index 5724b5432..e9cd883b2 100644 --- a/src/app/components/empty-list-marker/empty-list-marker.component.spec.ts +++ b/src/app/components/empty-list-marker/empty-list-marker.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { EmptyListMarkerComponent } from './empty-list-marker.component'; @@ -9,6 +10,7 @@ describe('EmptyListMarkerComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [EmptyListMarkerComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); diff --git a/src/app/components/footer/footer.component.spec.ts b/src/app/components/footer/footer.component.spec.ts index c3507d1df..482c0ef6e 100644 --- a/src/app/components/footer/footer.component.spec.ts +++ b/src/app/components/footer/footer.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { FooterComponent } from './footer.component'; @@ -9,6 +10,7 @@ describe('FooterComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [FooterComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); })); diff --git a/src/app/components/header/header.component.scss b/src/app/components/header/header.component.scss index 8bdd32836..23feea97a 100644 --- a/src/app/components/header/header.component.scss +++ b/src/app/components/header/header.component.scss @@ -26,7 +26,7 @@ border-bottom-width: 0 !important; .dark &, .dark &:hover { - color: colors.color(primary) !important; + color: colors.color(primary-dark) !important; } .light &, .light &:hover { diff --git a/src/app/components/header/header.component.spec.ts b/src/app/components/header/header.component.spec.ts index 27130aff1..72363ba34 100644 --- a/src/app/components/header/header.component.spec.ts +++ b/src/app/components/header/header.component.spec.ts @@ -1,14 +1,54 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; +import { provideHttpClient } from '@angular/common/http'; +import { provideRouter } from '@angular/router'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { of } from 'rxjs'; import { HeaderComponent } from './header.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { AuthenticationService } from 'src/app/services/connectors/authentication/authentication.service'; +import { createMockRestApiConnector } from 'src/app/testing/mocks/rest-api-connector.mock'; +import { createMockAuthenticationService } from 'src/app/testing/mocks/authentication-service.mock'; describe('HeaderComponent', () => { let component: HeaderComponent; let fixture: ComponentFixture; beforeEach(waitForAsync(() => { + const mockRestApiConnector = createMockRestApiConnector({}); + const mockAuthService = createMockAuthenticationService({}); + TestBed.configureTestingModule({ declarations: [HeaderComponent], + imports: [ + MatToolbarModule, + MatMenuModule, + MatButtonModule, + MatIconModule, + MatTooltipModule, + NoopAnimationsModule, + ], + providers: [ + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + { provide: AuthenticationService, useValue: mockAuthService }, + provideRouter([]), + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + }, + }, + provideHttpClient(), + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); })); diff --git a/src/app/components/icon-view/icon-view.component.spec.ts b/src/app/components/icon-view/icon-view.component.spec.ts index fc12eba9b..27d763dac 100644 --- a/src/app/components/icon-view/icon-view.component.spec.ts +++ b/src/app/components/icon-view/icon-view.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { IconViewComponent } from './icon-view.component'; @@ -9,12 +10,18 @@ describe('WorkflowPropertyComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [IconViewComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(IconViewComponent); component = fixture.componentInstance; + // Set required config input + component.config = { + field: 'test', + object: {} as any, + }; fixture.detectChanges(); }); diff --git a/src/app/components/loading-overlay/loading-overlay.component.html b/src/app/components/loading-overlay/loading-overlay.component.html index d93de2774..00f62c5da 100644 --- a/src/app/components/loading-overlay/loading-overlay.component.html +++ b/src/app/components/loading-overlay/loading-overlay.component.html @@ -1,6 +1,35 @@
-
+
+ + +
+ + +
{{ progress }}%
+
+ + +
+
+
+ {{ phase.label }} +
+
+ + +
{{ phase.progress }}%
+
+
+
+
{{ message }}
diff --git a/src/app/components/loading-overlay/loading-overlay.component.scss b/src/app/components/loading-overlay/loading-overlay.component.scss index 68d0e5207..c1465f977 100644 --- a/src/app/components/loading-overlay/loading-overlay.component.scss +++ b/src/app/components/loading-overlay/loading-overlay.component.scss @@ -6,6 +6,50 @@ justify-content: center; align-items: center; } + .progress-wrapper { + width: 100%; + max-width: 400px; + margin: 0 auto; + .progress-text { + margin-top: 8px; + font-size: 14px; + font-weight: 500; + color: rgba(0, 0, 0, 0.6); + } + } + .multi-phase-progress { + width: 100%; + max-width: 500px; + margin: 0 auto; + .phase-progress-item { + margin-bottom: 20px; + &:last-child { + margin-bottom: 0; + } + .phase-label { + font-size: 14px; + font-weight: 500; + color: rgba(0, 0, 0, 0.4); + margin-bottom: 6px; + text-align: left; + &.active { + color: rgba(0, 0, 0, 0.87); + font-weight: 600; + } + } + .phase-bar-container { + position: relative; + .phase-progress-text { + position: absolute; + right: 0; + top: -20px; + font-size: 12px; + font-weight: 500; + color: rgba(0, 0, 0, 0.6); + } + } + } + } text-align: center; .text-label { margin-top: 12px; diff --git a/src/app/components/loading-overlay/loading-overlay.component.spec.ts b/src/app/components/loading-overlay/loading-overlay.component.spec.ts index ca7b534dc..e9a92d5a9 100644 --- a/src/app/components/loading-overlay/loading-overlay.component.spec.ts +++ b/src/app/components/loading-overlay/loading-overlay.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { LoadingOverlayComponent } from './loading-overlay.component'; @@ -9,6 +10,7 @@ describe('LoadingOverlayComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [LoadingOverlayComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); })); diff --git a/src/app/components/loading-overlay/loading-overlay.component.ts b/src/app/components/loading-overlay/loading-overlay.component.ts index b930feb06..787f3a2f0 100644 --- a/src/app/components/loading-overlay/loading-overlay.component.ts +++ b/src/app/components/loading-overlay/loading-overlay.component.ts @@ -1,5 +1,12 @@ import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core'; +export interface PhaseProgress { + phase: string; + label: string; + progress: number; + active: boolean; +} + @Component({ selector: 'app-loading-overlay', templateUrl: './loading-overlay.component.html', @@ -9,6 +16,9 @@ import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core'; }) export class LoadingOverlayComponent implements OnInit { @Input() message = ''; + @Input() showProgress = false; + @Input() progress = 0; // 0-100 (single progress bar) + @Input() phaseProgress: PhaseProgress[] = []; // Multiple progress bars constructor() { // intentionally left blank diff --git a/src/app/components/markdown-view-dialog/markdown-view-dialog.component.spec.ts b/src/app/components/markdown-view-dialog/markdown-view-dialog.component.spec.ts index 053d91e33..1772778b5 100644 --- a/src/app/components/markdown-view-dialog/markdown-view-dialog.component.spec.ts +++ b/src/app/components/markdown-view-dialog/markdown-view-dialog.component.spec.ts @@ -1,4 +1,6 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { MarkdownViewDialogComponent } from './markdown-view-dialog.component'; @@ -9,6 +11,16 @@ describe('MarkdownViewDialogComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [MarkdownViewDialogComponent], + providers: [ + { provide: MatDialogRef, useValue: {} }, + { + provide: MAT_DIALOG_DATA, + useValue: { + markdown: 'Test markdown', + }, + }, + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); diff --git a/src/app/components/matrix/tactic-cell/tactic-cell.component.spec.ts b/src/app/components/matrix/tactic-cell/tactic-cell.component.spec.ts index 14232b208..1b56b407a 100644 --- a/src/app/components/matrix/tactic-cell/tactic-cell.component.spec.ts +++ b/src/app/components/matrix/tactic-cell/tactic-cell.component.spec.ts @@ -1,4 +1,6 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { Tactic } from 'src/app/classes/stix/tactic'; import { TacticCellComponent } from './tactic-cell.component'; @@ -9,12 +11,20 @@ describe('TacticCellComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [TacticCellComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(TacticCellComponent); component = fixture.componentInstance; + + // Initialize the required tactic input with a mock + const mockTactic = new Tactic(); + mockTactic.attackID = 'TA0001'; + mockTactic.name = 'Test Tactic'; + component.tactic = mockTactic; + fixture.detectChanges(); }); diff --git a/src/app/components/matrix/technique-cell/technique-cell.component.spec.ts b/src/app/components/matrix/technique-cell/technique-cell.component.spec.ts index 64871ce85..6400f38bc 100644 --- a/src/app/components/matrix/technique-cell/technique-cell.component.spec.ts +++ b/src/app/components/matrix/technique-cell/technique-cell.component.spec.ts @@ -1,6 +1,8 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { TechniqueCellComponent } from './technique-cell.component'; +import { Technique } from 'src/app/classes/stix/technique'; describe('TechniqueCellComponent', () => { let component: TechniqueCellComponent; @@ -9,12 +11,21 @@ describe('TechniqueCellComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [TechniqueCellComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(TechniqueCellComponent); component = fixture.componentInstance; + + // Initialize the required technique input with a mock + const mockTechnique = new Technique(); + mockTechnique.attackID = 'T1234'; + mockTechnique.name = 'Test Technique'; + mockTechnique.subTechniques = []; + component.technique = mockTechnique; + fixture.detectChanges(); }); diff --git a/src/app/components/multiple-choice-dialog/multiple-choice-dialog.component.spec.ts b/src/app/components/multiple-choice-dialog/multiple-choice-dialog.component.spec.ts index 34467ddd1..d910560d9 100644 --- a/src/app/components/multiple-choice-dialog/multiple-choice-dialog.component.spec.ts +++ b/src/app/components/multiple-choice-dialog/multiple-choice-dialog.component.spec.ts @@ -1,4 +1,6 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { MultipleChoiceDialogComponent } from './multiple-choice-dialog.component'; @@ -9,6 +11,17 @@ describe('MultipleChoiceDialogComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [MultipleChoiceDialogComponent], + providers: [ + { provide: MatDialogRef, useValue: {} }, + { + provide: MAT_DIALOG_DATA, + useValue: { + title: 'Test Title', + choices: [{ label: 'Choice 1' }], + }, + }, + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); diff --git a/src/app/components/object-status/object-status.component.spec.ts b/src/app/components/object-status/object-status.component.spec.ts index fdfac4712..b22918642 100644 --- a/src/app/components/object-status/object-status.component.spec.ts +++ b/src/app/components/object-status/object-status.component.spec.ts @@ -1,4 +1,18 @@ +import { DragDropModule } from '@angular/cdk/drag-drop'; +import { provideHttpClient } from '@angular/common/http'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatSelectModule } from '@angular/material/select'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { ActivatedRoute } from '@angular/router'; +import { MtxPopoverModule } from '@ng-matero/extensions/popover'; +import { of } from 'rxjs'; import { ObjectStatusComponent } from './object-status.component'; @@ -9,6 +23,30 @@ describe('ObjectStatusComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ObjectStatusComponent], + imports: [ + MtxPopoverModule, + ReactiveFormsModule, + FormsModule, + MatFormFieldModule, + MatSelectModule, + MatCheckboxModule, + MatIconModule, + MatButtonModule, + MatTooltipModule, + DragDropModule, + BrowserAnimationsModule, + ], + providers: [ + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + }, + }, + provideHttpClient(), + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); diff --git a/src/app/components/object-status/object-status.component.ts b/src/app/components/object-status/object-status.component.ts index 8392eb054..43269603e 100644 --- a/src/app/components/object-status/object-status.component.ts +++ b/src/app/components/object-status/object-status.component.ts @@ -10,6 +10,7 @@ import { AddDialogComponent } from '../add-dialog/add-dialog.component'; import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component'; import { forkJoin } from 'rxjs'; import { WorkflowStates } from 'src/app/utils/types'; +import { SaveDialogComponent } from '../save-dialog/save-dialog.component'; @Component({ selector: 'app-object-status', @@ -144,10 +145,29 @@ export class ObjectStatusComponent implements OnInit { * @param event workflow state selection */ public workflowChange(event) { + const previousWorkflowState = + this.object.workflow?.state || 'work-in-progress'; if (event.isUserInput) { - if (event.source.value == 'none') this.object.workflow = undefined; - else this.object.workflow = { state: event.source.value }; - this.save(); + // Open save-dialog instead of saving directly + const dialogRef = this.dialog.open(SaveDialogComponent, { + maxWidth: '70em', + maxHeight: '70em', + data: { + object: this.object, + versionAlreadyIncremented: false, + // Pass the selected workflow state + initialWorkflowState: event.source.value, + }, + autoFocus: false, + }); + + dialogRef.afterClosed().subscribe(result => { + if (result) { + this.editorService.onReload.emit(); + } else { + this.statusControl.setValue(previousWorkflowState); + } + }); } } diff --git a/src/app/components/outdated-content-warning/outdated-content-warning.component.spec.ts b/src/app/components/outdated-content-warning/outdated-content-warning.component.spec.ts index 2c2b21546..2c95de1f2 100644 --- a/src/app/components/outdated-content-warning/outdated-content-warning.component.spec.ts +++ b/src/app/components/outdated-content-warning/outdated-content-warning.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { OutdatedContentWarningComponent } from './outdated-content-warning.component'; @@ -9,6 +10,7 @@ describe('OutdatedContentWarningComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [OutdatedContentWarningComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); fixture = TestBed.createComponent(OutdatedContentWarningComponent); diff --git a/src/app/components/property-diff/property-diff.component.spec.ts b/src/app/components/property-diff/property-diff.component.spec.ts index cbb81e887..cb50cfaaa 100644 --- a/src/app/components/property-diff/property-diff.component.spec.ts +++ b/src/app/components/property-diff/property-diff.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { PropertyDiffComponent } from './property-diff.component'; @@ -9,10 +10,16 @@ describe('PropertyDiffComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [PropertyDiffComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); fixture = TestBed.createComponent(PropertyDiffComponent); component = fixture.componentInstance; + + // Initialize required inputs to prevent null errors + component.before = 'old value'; + component.after = 'new value'; + fixture.detectChanges(); }); diff --git a/src/app/components/recent-activity/recent-activity.component.spec.ts b/src/app/components/recent-activity/recent-activity.component.spec.ts index 8ccb2ad49..31f370179 100644 --- a/src/app/components/recent-activity/recent-activity.component.spec.ts +++ b/src/app/components/recent-activity/recent-activity.component.spec.ts @@ -1,4 +1,6 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; import { RecentActivityComponent } from './recent-activity.component'; @@ -9,12 +11,15 @@ describe('RecentActivityComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [RecentActivityComponent], + providers: [provideHttpClient()], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(RecentActivityComponent); component = fixture.componentInstance; + component.identities = []; fixture.detectChanges(); }); diff --git a/src/app/components/reference-edit-dialog/reference-edit-dialog.component.spec.ts b/src/app/components/reference-edit-dialog/reference-edit-dialog.component.spec.ts index d7917dbc6..2c17ef7d3 100644 --- a/src/app/components/reference-edit-dialog/reference-edit-dialog.component.spec.ts +++ b/src/app/components/reference-edit-dialog/reference-edit-dialog.component.spec.ts @@ -1,14 +1,48 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { provideHttpClient } from '@angular/common/http'; import { ReferenceEditDialogComponent } from './reference-edit-dialog.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createAsyncObservable, + createMockRestApiConnector, + createPaginatedResponse, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('ReferenceEditDialogComponent', () => { let component: ReferenceEditDialogComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getAllReferences: () => + createAsyncObservable(createPaginatedResponse([])), + getRelatedTo: () => createAsyncObservable(createPaginatedResponse([])), + putReference: () => createAsyncObservable({}), + postReference: () => createAsyncObservable({}), + }); + await TestBed.configureTestingModule({ declarations: [ReferenceEditDialogComponent], + providers: [ + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + { + provide: MAT_DIALOG_DATA, + useValue: { + mode: 'view', + }, + }, + { + provide: MatDialogRef, + useValue: { + close: () => {}, + }, + }, + provideHttpClient(), + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); diff --git a/src/app/components/resources-drawer/history-timeline/history-timeline.component.spec.ts b/src/app/components/resources-drawer/history-timeline/history-timeline.component.spec.ts index e87e71181..5bf72472c 100644 --- a/src/app/components/resources-drawer/history-timeline/history-timeline.component.spec.ts +++ b/src/app/components/resources-drawer/history-timeline/history-timeline.component.spec.ts @@ -1,14 +1,62 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { ActivatedRoute, Router } from '@angular/router'; +import { of } from 'rxjs'; import { HistoryTimelineComponent } from './history-timeline.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createAsyncObservable, + createMockRestApiConnector, + createPaginatedResponse, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('HistoryTimelineComponent', () => { let component: HistoryTimelineComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getSoftware: () => createAsyncObservable([]), + getGroup: () => createAsyncObservable([]), + getMatrix: () => createAsyncObservable([]), + getMitigation: () => createAsyncObservable([]), + getTactic: () => createAsyncObservable([]), + getCampaign: () => createAsyncObservable([]), + getTechnique: () => createAsyncObservable([]), + getCollection: () => createAsyncObservable([]), + getDataSource: () => createAsyncObservable([]), + getDataComponent: () => createAsyncObservable([]), + getAsset: () => createAsyncObservable([]), + getAnalytic: () => createAsyncObservable([]), + getDetectionStrategy: () => createAsyncObservable([]), + getRelatedTo: () => createAsyncObservable(createPaginatedResponse([])), + getAllCollections: () => + createAsyncObservable(createPaginatedResponse([])), + }); + await TestBed.configureTestingModule({ declarations: [HistoryTimelineComponent], + providers: [ + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + provideHttpClient(), + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + }, + }, + { + provide: Router, + useValue: { + url: '/technique/mock-stix-id?param=value', + events: of({}), + }, + }, + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); diff --git a/src/app/components/resources-drawer/notes-editor/notes-editor.component.spec.ts b/src/app/components/resources-drawer/notes-editor/notes-editor.component.spec.ts index a0d313898..d74217131 100644 --- a/src/app/components/resources-drawer/notes-editor/notes-editor.component.spec.ts +++ b/src/app/components/resources-drawer/notes-editor/notes-editor.component.spec.ts @@ -1,14 +1,38 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { Router } from '@angular/router'; import { NotesEditorComponent } from './notes-editor.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createMockRestApiConnector, + createAsyncObservable, + createPaginatedResponse, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('NotesEditorComponent', () => { let component: NotesEditorComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getAllNotes: () => createAsyncObservable(createPaginatedResponse()), + }); + await TestBed.configureTestingModule({ declarations: [NotesEditorComponent], + providers: [ + provideHttpClient(), + { + provide: Router, + useValue: { + url: '/test/mock-id?param=value', + }, + }, + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); diff --git a/src/app/components/resources-drawer/reference-sidebar/reference-sidebar.component.spec.ts b/src/app/components/resources-drawer/reference-sidebar/reference-sidebar.component.spec.ts index 19b9fa31c..0db36e5a5 100644 --- a/src/app/components/resources-drawer/reference-sidebar/reference-sidebar.component.spec.ts +++ b/src/app/components/resources-drawer/reference-sidebar/reference-sidebar.component.spec.ts @@ -1,14 +1,32 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; import { ReferenceSidebarComponent } from './reference-sidebar.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createAsyncObservable, + createMockRestApiConnector, + createPaginatedResponse, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('ReferenceManagerComponent', () => { let component: ReferenceSidebarComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getAllReferences: () => + createAsyncObservable(createPaginatedResponse([])), + }); + await TestBed.configureTestingModule({ declarations: [ReferenceSidebarComponent], + providers: [ + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + provideHttpClient(), + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); diff --git a/src/app/components/resources-drawer/resources-drawer.component.spec.ts b/src/app/components/resources-drawer/resources-drawer.component.spec.ts index 94b7befe4..384033eb4 100644 --- a/src/app/components/resources-drawer/resources-drawer.component.spec.ts +++ b/src/app/components/resources-drawer/resources-drawer.component.spec.ts @@ -1,14 +1,25 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; import { ResourcesDrawerComponent } from './resources-drawer.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { createMockRestApiConnector } from 'src/app/testing/mocks/rest-api-connector.mock'; describe('ResourcesDrawerComponent', () => { let component: ResourcesDrawerComponent; let fixture: ComponentFixture; beforeEach(waitForAsync(() => { + const mockRestApiConnector = createMockRestApiConnector({}); + TestBed.configureTestingModule({ declarations: [ResourcesDrawerComponent], + providers: [ + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + provideHttpClient(), + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); })); diff --git a/src/app/components/resources-drawer/search/search.component.spec.ts b/src/app/components/resources-drawer/search/search.component.spec.ts index bb8443bfa..c73665fb7 100644 --- a/src/app/components/resources-drawer/search/search.component.spec.ts +++ b/src/app/components/resources-drawer/search/search.component.spec.ts @@ -1,14 +1,40 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { ActivatedRoute } from '@angular/router'; +import { of } from 'rxjs'; import { SearchComponent } from './search.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createAsyncObservable, + createMockRestApiConnector, + createPaginatedResponse, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('SearchComponent', () => { let component: SearchComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getAllObjects: () => createAsyncObservable(createPaginatedResponse([])), + }); + await TestBed.configureTestingModule({ declarations: [SearchComponent], + providers: [ + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + provideHttpClient(), + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + }, + }, + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); fixture = TestBed.createComponent(SearchComponent); diff --git a/src/app/components/save-dialog/save-dialog.component.html b/src/app/components/save-dialog/save-dialog.component.html index a64a2d839..4783abb04 100644 --- a/src/app/components/save-dialog/save-dialog.component.html +++ b/src/app/components/save-dialog/save-dialog.component.html @@ -17,12 +17,14 @@

Validation

mark as... - + {{ workflow[1] }} + [value]="workflow[0]"> + {{ workflow[1] }} + - -
diff --git a/src/app/components/stix/attackid-property/attackid-edit/attackid-edit.component.scss b/src/app/components/stix/attackid-property/attackid-edit/attackid-edit.component.scss deleted file mode 100644 index ef633d38a..000000000 --- a/src/app/components/stix/attackid-property/attackid-edit/attackid-edit.component.scss +++ /dev/null @@ -1,8 +0,0 @@ -.attackid-edit { - .mat-mdc-form-field { - width: 100%; - } - .mat-mdc-form-field-subscript-wrapper { - display: none; - } -} diff --git a/src/app/components/stix/attackid-property/attackid-edit/attackid-edit.component.spec.ts b/src/app/components/stix/attackid-property/attackid-edit/attackid-edit.component.spec.ts deleted file mode 100644 index e7ac86189..000000000 --- a/src/app/components/stix/attackid-property/attackid-edit/attackid-edit.component.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { AttackidEditComponent } from './attackid-edit.component'; - -describe('AttackidEditComponent', () => { - let component: AttackidEditComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [AttackidEditComponent], - }).compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(AttackidEditComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/components/stix/attackid-property/attackid-edit/attackid-edit.component.ts b/src/app/components/stix/attackid-property/attackid-edit/attackid-edit.component.ts deleted file mode 100644 index 42f62d589..000000000 --- a/src/app/components/stix/attackid-property/attackid-edit/attackid-edit.component.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { - Component, - Input, - OnInit, - Output, - ViewEncapsulation, - EventEmitter, -} from '@angular/core'; -import { AttackIDPropertyConfig } from '../attackid-property.component'; -import { RestApiConnectorService } from '../../../../services/connectors/rest-api/rest-api-connector.service'; -import { StixObject } from '../../../../classes/stix/stix-object'; - -@Component({ - selector: 'app-attackid-edit', - templateUrl: './attackid-edit.component.html', - styleUrls: ['./attackid-edit.component.scss'], - encapsulation: ViewEncapsulation.None, - standalone: false, -}) -export class AttackIDEditComponent implements OnInit { - @Input() public config: AttackIDPropertyConfig; - @Output() public attackIdGenerated = new EventEmitter(); - public prefix = ''; - - constructor(public restApiConnector: RestApiConnectorService) {} - - ngOnInit(): void { - // Get namespace settings and prepend, if creating a new object - if ((this.config.object as StixObject).firstInitialized) { - const namespaceSub = this.restApiConnector - .getOrganizationNamespace() - .subscribe({ - next: namespaceSettings => { - this.prefix = namespaceSettings.prefix ?? ''; - }, - complete: () => namespaceSub.unsubscribe(), - }); - } else { - // Otherwise extract existing prefix, if any - const found = (this.config.object as StixObject).attackID.match( - /[A-Z]+-/g - ); - if (found) { - this.prefix = found[0].replace(/-$/, ''); - } - } - } - - public handleGenerateClick(): void { - if ((this.config.object as StixObject).supportsAttackID) { - const sub = (this.config.object as StixObject) - .generateAttackId(this.restApiConnector, this.prefix) - .subscribe({ - next: val => { - (this.config.object as StixObject).attackID = val; - }, - complete: () => { - this.attackIdChanged(); - sub.unsubscribe(); - }, - }); - } - } - - public attackIdChanged(): void { - this.attackIdGenerated.emit(); - } - - public formatAttackId(): void { - // handle user set attack id - const object = this.config.object as StixObject; - const withPrefix = object.formatWithPrefix(object.attackID, this.prefix); - (this.config.object as StixObject).attackID = withPrefix; - } -} diff --git a/src/app/components/stix/attackid-property/attackid-property.component.html b/src/app/components/stix/attackid-property/attackid-property.component.html index 729863f4c..2e6ed69de 100644 --- a/src/app/components/stix/attackid-property/attackid-property.component.html +++ b/src/app/components/stix/attackid-property/attackid-property.component.html @@ -1,32 +1,35 @@
-
-
- - + @if (config.mode === 'diff') { +
+
+ +
+ ATT&CK ID
- ATT&CK ID -
- - + } @else { +
+
+ {{ config.object['attackID'] }} + @if (config.object['attackID']) { + + } +
+ ATT&CK ID +
+ }
diff --git a/src/app/components/stix/attackid-property/attackid-property.component.spec.ts b/src/app/components/stix/attackid-property/attackid-property.component.spec.ts index c8537d42f..51209f7f4 100644 --- a/src/app/components/stix/attackid-property/attackid-property.component.spec.ts +++ b/src/app/components/stix/attackid-property/attackid-property.component.spec.ts @@ -1,20 +1,27 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { AttackidPropertyComponent } from './attackid-property.component'; +import { AttackIDPropertyComponent } from './attackid-property.component'; -describe('AttackidPropertyComponent', () => { - let component: AttackidPropertyComponent; - let fixture: ComponentFixture; +describe('AttackIDPropertyComponent', () => { + let component: AttackIDPropertyComponent; + let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [AttackidPropertyComponent], + declarations: [AttackIDPropertyComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { - fixture = TestBed.createComponent(AttackidPropertyComponent); + fixture = TestBed.createComponent(AttackIDPropertyComponent); component = fixture.componentInstance; + // Set required config input + component.config = { + mode: 'view', + object: {} as any, + }; fixture.detectChanges(); }); diff --git a/src/app/components/stix/attackid-property/attackid-property.component.ts b/src/app/components/stix/attackid-property/attackid-property.component.ts index f688495bc..59d3ef0df 100644 --- a/src/app/components/stix/attackid-property/attackid-property.component.ts +++ b/src/app/components/stix/attackid-property/attackid-property.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { Component, Input } from '@angular/core'; import { MatSnackBar } from '@angular/material/snack-bar'; import { StixObject } from 'src/app/classes/stix/stix-object'; @@ -10,7 +10,14 @@ import { StixObject } from 'src/app/classes/stix/stix-object'; }) export class AttackIDPropertyComponent { @Input() public config: AttackIDPropertyConfig; - @Output() public attackIdGenerated = new EventEmitter(); + + public get currentId(): string { + return this.config.object[0]?.['attackID'] || ''; + } + + public get previousId(): string { + return this.config.object[1]?.['attackID'] || ''; + } public get linkById(): string { return `(LinkById: ${this.config.object['attackID']})`; @@ -19,21 +26,15 @@ export class AttackIDPropertyComponent { constructor(public snackbar: MatSnackBar) { // intentionally left blank } - - public onAttackIdGenerated(): void { - this.attackIdGenerated.emit(); - } } export interface AttackIDPropertyConfig { /* What is the current mode? Default: 'view - * view: viewing the list property - * edit: editing the list property + * view: viewing the attack id property * diff: displaying the diff between two STIX objects. If this mode is selected, two StixObjects must be specified in the objects field */ - mode?: 'view' | 'edit' | 'diff'; + mode?: 'view' | 'diff'; /* The object to show the field of * Note: if mode is diff, pass an array of two objects to diff */ object: StixObject | [StixObject, StixObject]; - required?: boolean; // default false } diff --git a/src/app/components/stix/attackid-property/attackid-view/attackid-view.component.html b/src/app/components/stix/attackid-property/attackid-view/attackid-view.component.html deleted file mode 100644 index 17e8dadff..000000000 --- a/src/app/components/stix/attackid-property/attackid-view/attackid-view.component.html +++ /dev/null @@ -1,3 +0,0 @@ -
- {{ config.object['attackID'] }} -
diff --git a/src/app/components/stix/attackid-property/attackid-view/attackid-view.component.spec.ts b/src/app/components/stix/attackid-property/attackid-view/attackid-view.component.spec.ts deleted file mode 100644 index e411777de..000000000 --- a/src/app/components/stix/attackid-property/attackid-view/attackid-view.component.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { AttackIDViewComponent } from './attackid-view.component'; - -describe('AttackidViewComponent', () => { - let component: AttackIDViewComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [AttackIDViewComponent], - }).compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(AttackIDViewComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/components/stix/attackid-property/attackid-view/attackid-view.component.ts b/src/app/components/stix/attackid-property/attackid-view/attackid-view.component.ts deleted file mode 100644 index e7e8c6767..000000000 --- a/src/app/components/stix/attackid-property/attackid-view/attackid-view.component.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Component, Input, OnInit } from '@angular/core'; -import { AttackIDPropertyConfig } from '../attackid-property.component'; - -@Component({ - selector: 'app-attackid-view', - templateUrl: './attackid-view.component.html', - styleUrls: ['./attackid-view.component.scss'], - standalone: false, -}) -export class AttackIDViewComponent implements OnInit { - @Input() public config: AttackIDPropertyConfig; - - constructor() { - // intentionally left blank - } - - ngOnInit(): void { - // intentionally left blank - } -} diff --git a/src/app/components/stix/boolean-property/boolean-property.component.spec.ts b/src/app/components/stix/boolean-property/boolean-property.component.spec.ts index 5642203e8..0aafa26cd 100644 --- a/src/app/components/stix/boolean-property/boolean-property.component.spec.ts +++ b/src/app/components/stix/boolean-property/boolean-property.component.spec.ts @@ -1,6 +1,8 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { BooleanPropertyComponent } from './boolean-property.component'; +import { StixObject } from 'src/app/classes/stix'; describe('BooleanPropertyComponent', () => { let component: BooleanPropertyComponent; @@ -9,10 +11,18 @@ describe('BooleanPropertyComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [BooleanPropertyComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); fixture = TestBed.createComponent(BooleanPropertyComponent); component = fixture.componentInstance; + // Set required config input + component.config = { + mode: 'view', + object: {} as StixObject, + field: 'test', + label: 'Test', + }; fixture.detectChanges(); }); diff --git a/src/app/components/stix/citation-property/citation-diff/citation-diff.component.spec.ts b/src/app/components/stix/citation-property/citation-diff/citation-diff.component.spec.ts index 24668a21e..8ac4c42ca 100644 --- a/src/app/components/stix/citation-property/citation-diff/citation-diff.component.spec.ts +++ b/src/app/components/stix/citation-property/citation-diff/citation-diff.component.spec.ts @@ -1,6 +1,8 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { CitationDiffComponent } from './citation-diff.component'; +import { StixObject } from 'src/app/classes/stix/stix-object'; describe('CitationDiffComponent', () => { let component: CitationDiffComponent; @@ -9,10 +11,19 @@ describe('CitationDiffComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [CitationDiffComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); fixture = TestBed.createComponent(CitationDiffComponent); component = fixture.componentInstance; + // Set required config input + component.config = { + mode: 'diff', + object: [{} as StixObject, {} as StixObject], + field: 'test', + referencesField: 'external_references', + label: 'Test', + }; fixture.detectChanges(); }); diff --git a/src/app/components/stix/citation-property/citation-edit/citation-edit.component.spec.ts b/src/app/components/stix/citation-property/citation-edit/citation-edit.component.spec.ts index feb698f5d..49b41fe11 100644 --- a/src/app/components/stix/citation-property/citation-edit/citation-edit.component.spec.ts +++ b/src/app/components/stix/citation-property/citation-edit/citation-edit.component.spec.ts @@ -1,21 +1,47 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { provideRouter } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { of } from 'rxjs'; import { CitationEditComponent } from './citation-edit.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { createMockRestApiConnector } from 'src/app/testing/mocks/rest-api-connector.mock'; describe('CitationEditComponent', () => { let component: CitationEditComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({}); + await TestBed.configureTestingModule({ declarations: [CitationEditComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + provideHttpClient(), + provideRouter([]), + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + }, + }, + ], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(CitationEditComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { + mode: 'edit', + object: {} as any, + field: 'external_references', + } as any; }); it('should create', () => { diff --git a/src/app/components/stix/citation-property/citation-property.component.spec.ts b/src/app/components/stix/citation-property/citation-property.component.spec.ts index d8ce1719d..dd5e23fa4 100644 --- a/src/app/components/stix/citation-property/citation-property.component.spec.ts +++ b/src/app/components/stix/citation-property/citation-property.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { CitationPropertyComponent } from './citation-property.component'; @@ -9,12 +10,20 @@ describe('CitationPropertyComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [CitationPropertyComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(CitationPropertyComponent); component = fixture.componentInstance; + component.config = { + mode: 'view', + object: {} as any, + field: 'description', + referencesField: 'external_references', + label: 'Description', + }; fixture.detectChanges(); }); diff --git a/src/app/components/stix/citation-property/citation-view/citation-view.component.spec.ts b/src/app/components/stix/citation-property/citation-view/citation-view.component.spec.ts index 055bcc256..04bdb576c 100644 --- a/src/app/components/stix/citation-property/citation-view/citation-view.component.spec.ts +++ b/src/app/components/stix/citation-property/citation-view/citation-view.component.spec.ts @@ -1,4 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { CitationViewComponent } from './citation-view.component'; @@ -9,13 +11,21 @@ describe('CitationViewComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [CitationViewComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [provideHttpClient()], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(CitationViewComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { + mode: 'view', + object: {} as any, + field: 'external_references', + referencesField: 'external_references', + label: 'External References', + }; }); it('should create', () => { diff --git a/src/app/components/stix/datepicker-property/datepicker-property.component.spec.ts b/src/app/components/stix/datepicker-property/datepicker-property.component.spec.ts index 472fc1b4b..1997116a6 100644 --- a/src/app/components/stix/datepicker-property/datepicker-property.component.spec.ts +++ b/src/app/components/stix/datepicker-property/datepicker-property.component.spec.ts @@ -1,4 +1,6 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; import { DatepickerPropertyComponent } from './datepicker-property.component'; @@ -9,12 +11,19 @@ describe('DatepickerPropertyComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [DatepickerPropertyComponent], + providers: [provideHttpClient()], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(DatepickerPropertyComponent); component = fixture.componentInstance; + // Set required config input + component.config = { + object: {} as any, + field: 'date', + }; fixture.detectChanges(); }); diff --git a/src/app/components/stix/descriptive-property/descriptive-diff/descriptive-diff.component.spec.ts b/src/app/components/stix/descriptive-property/descriptive-diff/descriptive-diff.component.spec.ts index cc4002356..39597f648 100644 --- a/src/app/components/stix/descriptive-property/descriptive-diff/descriptive-diff.component.spec.ts +++ b/src/app/components/stix/descriptive-property/descriptive-diff/descriptive-diff.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { DescriptiveDiffComponent } from './descriptive-diff.component'; @@ -9,11 +10,18 @@ describe('DescriptiveDiffComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [DescriptiveDiffComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); + }); + beforeEach(() => { fixture = TestBed.createComponent(DescriptiveDiffComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { + mode: 'diff', + object: [{} as any, {} as any], + field: 'description', + }; }); it('should create', () => { diff --git a/src/app/components/stix/descriptive-property/descriptive-edit/descriptive-edit.component.spec.ts b/src/app/components/stix/descriptive-property/descriptive-edit/descriptive-edit.component.spec.ts index 9f25b5f5d..09ea78456 100644 --- a/src/app/components/stix/descriptive-property/descriptive-edit/descriptive-edit.component.spec.ts +++ b/src/app/components/stix/descriptive-property/descriptive-edit/descriptive-edit.component.spec.ts @@ -1,21 +1,47 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { provideRouter } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { of } from 'rxjs'; import { DescriptiveEditComponent } from './descriptive-edit.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { createMockRestApiConnector } from 'src/app/testing/mocks/rest-api-connector.mock'; describe('DescriptiveEditComponent', () => { let component: DescriptiveEditComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({}); + await TestBed.configureTestingModule({ declarations: [DescriptiveEditComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + provideHttpClient(), + provideRouter([]), + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + }, + }, + ], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(DescriptiveEditComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { + mode: 'edit', + object: {} as any, + field: 'description', + }; }); it('should create', () => { diff --git a/src/app/components/stix/descriptive-property/descriptive-property.component.spec.ts b/src/app/components/stix/descriptive-property/descriptive-property.component.spec.ts index bbb0d1c37..4ae599875 100644 --- a/src/app/components/stix/descriptive-property/descriptive-property.component.spec.ts +++ b/src/app/components/stix/descriptive-property/descriptive-property.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { DescriptivePropertyComponent } from './descriptive-property.component'; @@ -9,12 +10,19 @@ describe('DescriptivePropertyComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [DescriptivePropertyComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(DescriptivePropertyComponent); component = fixture.componentInstance; + component.config = { + mode: 'view', + object: {} as any, + field: 'description', + label: 'Description', + }; fixture.detectChanges(); }); diff --git a/src/app/components/stix/descriptive-property/descriptive-view/descriptive-view.component.spec.ts b/src/app/components/stix/descriptive-property/descriptive-view/descriptive-view.component.spec.ts index 218d1d91c..91bfc0220 100644 --- a/src/app/components/stix/descriptive-property/descriptive-view/descriptive-view.component.spec.ts +++ b/src/app/components/stix/descriptive-property/descriptive-view/descriptive-view.component.spec.ts @@ -1,20 +1,35 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { DescriptiveViewComponent } from './descriptive-view.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { createMockRestApiConnector } from 'src/app/testing/mocks/rest-api-connector.mock'; describe('DescriptiveViewComponent', () => { let component: DescriptiveViewComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({}); + await TestBed.configureTestingModule({ declarations: [DescriptiveViewComponent], + providers: [ + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(DescriptiveViewComponent); component = fixture.componentInstance; + // Set required config input + component.config = { + mode: 'view', + object: {} as any, + field: 'description', + }; fixture.detectChanges(); }); diff --git a/src/app/components/stix/dictionary-property/dictionary-diff/dictionary-diff.component.html b/src/app/components/stix/dictionary-property/dictionary-diff/dictionary-diff.component.html new file mode 100644 index 000000000..c58972939 --- /dev/null +++ b/src/app/components/stix/dictionary-property/dictionary-diff/dictionary-diff.component.html @@ -0,0 +1,23 @@ +
+
+ + + + + + + + +
+ {{ column.label || column.name | titlecase }} + + + +
+
+ {{ config.label }} +
diff --git a/src/app/components/stix/attackid-property/attackid-view/attackid-view.component.scss b/src/app/components/stix/dictionary-property/dictionary-diff/dictionary-diff.component.scss similarity index 100% rename from src/app/components/stix/attackid-property/attackid-view/attackid-view.component.scss rename to src/app/components/stix/dictionary-property/dictionary-diff/dictionary-diff.component.scss diff --git a/src/app/components/stix/dictionary-property/dictionary-diff/dictionary-diff.component.ts b/src/app/components/stix/dictionary-property/dictionary-diff/dictionary-diff.component.ts new file mode 100644 index 000000000..89d9c2be8 --- /dev/null +++ b/src/app/components/stix/dictionary-property/dictionary-diff/dictionary-diff.component.ts @@ -0,0 +1,86 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { DictionaryPropertyConfig } from '../dictionary-property.component'; + +@Component({ + selector: 'app-dictionary-diff', + standalone: false, + templateUrl: './dictionary-diff.component.html', + styleUrl: './dictionary-diff.component.scss', +}) +export class DictionaryDiffComponent implements OnInit { + @Input() public config: DictionaryPropertyConfig; + + public get current() { + return this.config.object?.[0]?.[this.config.field] || []; + } + + public get previous() { + return this.config.object?.[1]?.[this.config.field] || []; + } + + public detailTable = []; + + public get columns() { + return this.config.columns; + } + + public get columnNames(): string[] { + return this.config.columns.map(c => c.name); + } + + public get columnsLabels(): string[] { + return this.config.columns.map(c => (c.label ? c.label : c.name)); + } + + ngOnInit(): void { + this.detailTable = this.mergeTable(); + } + + private mergeTable(): any[] { + const merged = new Map(); + + const prevCounts = new Map(); + const currCounts = new Map(); + + // add before state to map + for (const item of this.previous) { + const sig = this.rowSignature(item); + const n = (prevCounts.get(sig) ?? 0) + 1; + prevCounts.set(sig, n); + + const key = `${sig}__#${n}`; + merged.set(key, { before: item, after: null }); + } + + // add after state to map + for (const item of this.current) { + const sig = this.rowSignature(item); + const n = (currCounts.get(sig) ?? 0) + 1; + currCounts.set(sig, n); + + const key = `${sig}__#${n}`; + if (merged.has(key)) { + merged.get(key)!.after = item; + } else { + merged.set(key, { before: null, after: item }); + } + } + + return Array.from(merged.values()); + } + + private rowSignature(item): string { + return this.columnNames.map(col => this.normalize(item?.[col])).join('::'); + } + + private normalize(v: any): string { + if (v == null || v == undefined) return ''; + return String(v).trim().toLowerCase(); + } + + public valueToString(item, columnName) { + if (!item?.[columnName]) return ''; + if (Array.isArray(item[columnName])) return item[columnName].join('; '); + return String(item[columnName]); + } +} diff --git a/src/app/components/stix/dictionary-property/dictionary-edit/dictionary-edit.component.html b/src/app/components/stix/dictionary-property/dictionary-edit/dictionary-edit.component.html new file mode 100644 index 000000000..a76a7fd36 --- /dev/null +++ b/src/app/components/stix/dictionary-property/dictionary-edit/dictionary-edit.component.html @@ -0,0 +1,81 @@ +
+
+
+ +
+ + @if (config.object[config.field].length > 0) { + + + + + + + + + + + + + + +
+ {{ column.label || column.name }} + +
+
+ {{ row[column.name] }} +
+ @if (isEditing(rowIndex, column.name)) { +
+ + + +
+ } +
+
+
+ +
+
+ } +
+ {{ config.label }} +
diff --git a/src/app/components/stix/dictionary-property/dictionary-edit/dictionary-edit.component.scss b/src/app/components/stix/dictionary-property/dictionary-edit/dictionary-edit.component.scss new file mode 100644 index 000000000..38d420afc --- /dev/null +++ b/src/app/components/stix/dictionary-property/dictionary-edit/dictionary-edit.component.scss @@ -0,0 +1,131 @@ +@use '../../../../../style/globals'; +@use '../../../../../style/colors.scss'; + +.dictionary-edit { + @extend .labelled-box; + + .mat-divider { + margin: 16px 8px; + } + + .mat-mdc-form-field-subscript-wrapper { + display: none; // remove bottom padding used for hint + } + .mat-mdc-form-field-infix { + padding-bottom: 8px !important; + padding-top: 8px !important; + min-height: unset !important; + } + .mat-mdc-text-field-wrapper { + height: unset !important; + padding: 0 6px !important; + } + + table.property-table { + width: 100%; + table-layout: fixed; + } + td.mat-mdc-cell, + td.mat-cell, + .cell { + position: relative; + overflow: visible; + } + .cell { + cursor: text; + padding: 6px; + &:hover { + .light & { + background: colors.color-alternate(light); + } + .dark & { + background: colors.color-alternate(dark); + } + } + } + th.mat-column-actions, + td.mat-column-actions { + width: 44px; + min-width: 44px; + max-width: 44px; + text-align: center; + padding-left: 0; + padding-right: 0; + } + .action-cell { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + } + th.mat-column-name, + td.mat-column-name { + width: 30%; + max-width: 30%; + } + th.mat-mdc-header-cell, + td.mat-mdc-cell { + white-space: normal; + overflow-wrap: anywhere; + word-break: break-word; + // vertical-align: top; + vertical-align: middle; + } + .table-value { + white-space: normal; + overflow-wrap: anywhere; + + flex: 1; + height: 100%; + display: flex; + align-items: center; + word-break: break-word; + // min-height: 28px; + } + .cell-inner { + position: relative; + min-height: 40px; // give single-line edits room for the input field + display: flex; + align-items: center; + } + .table-value.hidden { + visibility: hidden; + } + .editor { + position: absolute; + inset: 0; + display: flex; + // align-items: flex-start; + align-items: center; + z-index: 2; + } + .editor-field { + width: 100%; + height: 100%; + } + .editor-field .mat-mdc-text-field-wrapper { + padding: 0 6px !important; + height: 100% !important; + display: flex; + align-items: center; + } + .editor-field .mat-mdc-form-field-infix { + padding-top: 0 !important; + padding-bottom: 0 !important; + min-height: unset !important; + + display: flex; + align-items: center; + } + .editor-field input.mat-mdc-input-element { + height: 100%; + line-height: normal; + } + .editor-field .mat-mdc-form-field-subscript-wrapper { + display: none; + } + .content { + overflow-x: auto; + display: block !important; + } +} diff --git a/src/app/components/stix/dictionary-property/dictionary-edit/dictionary-edit.component.ts b/src/app/components/stix/dictionary-property/dictionary-edit/dictionary-edit.component.ts new file mode 100644 index 000000000..be130770f --- /dev/null +++ b/src/app/components/stix/dictionary-property/dictionary-edit/dictionary-edit.component.ts @@ -0,0 +1,114 @@ +import { + Component, + Input, + OnInit, + ViewEncapsulation, + ViewChild, + ElementRef, + OnChanges, + SimpleChanges, +} from '@angular/core'; +import { DictionaryPropertyConfig } from '../dictionary-property.component'; +import { MatTable } from '@angular/material/table'; +import { EditorService } from 'src/app/services/editor/editor.service'; + +@Component({ + selector: 'app-dictionary-edit', + standalone: false, + templateUrl: './dictionary-edit.component.html', + styleUrls: ['./dictionary-edit.component.scss'], + encapsulation: ViewEncapsulation.None, +}) +export class DictionaryEditComponent implements OnInit, OnChanges { + @Input() public config: DictionaryPropertyConfig; + @ViewChild('editInput', { static: false }) editInput: ElementRef; + @ViewChild(MatTable) table!: MatTable; + + public displayedColumns: string[] = []; + public editingIdx: number | undefined; + public editingCol: string | undefined; + + private originalCellValue: any; + private originalTableValue: any; + + constructor(private editorService: EditorService) {} + + ngOnInit() { + this.originalTableValue = JSON.parse( + JSON.stringify(this.config.object[this.config.field]) + ); + + this.displayedColumns = [ + ...this.config.columns.map(c => c.name), + 'actions', + ]; + + this.sortRows(); + + this.editorService.onEditingStopped.subscribe(discard => { + if (discard) { + this.config.object[this.config.field] = this.originalTableValue; + } + }); + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes['config']) this.sortRows(); + } + + public sortRows(): void { + const sortColumn = this.config.columns.find(c => c.sort)?.name; + if (!sortColumn) return; + + this.config.object[this.config.field].sort((a, b) => { + const aValue = a[sortColumn] ?? ''; + const bValue = b[sortColumn] ?? ''; + return String(aValue).localeCompare(String(bValue), undefined, { + sensitivity: 'base', + }); + }); + } + + public isEditing(index: number, column: string): boolean { + return index === this.editingIdx && column === this.editingCol; + } + + public stopEditing(): void { + this.editingIdx = undefined; + this.editingCol = undefined; + this.originalCellValue = undefined; + } + + public cancelEditing(): void { + if (this.editingIdx === undefined || !this.editingCol) return; + const row = this.config.object[this.config.field]?.[this.editingIdx]; + if (row) row[this.editingCol] = this.originalCellValue; + this.stopEditing(); + } + + public addRow(): void { + const newRow: any = {}; + this.config.columns.forEach(c => (newRow[c.name] = '')); + this.config.object[this.config.field].unshift(newRow); + this.table?.renderRows(); + this.editCell(0, this.config.columns[0]?.name); + } + + public editCell(index: number, column: string): void { + if (this.isEditing(index, column)) return; + + this.editingIdx = index; + this.editingCol = column; + + const row = this.config.object[this.config.field]?.[index]; + this.originalCellValue = row ? row[column] : undefined; + + setTimeout(() => this.editInput?.nativeElement?.focus()); + } + + public deleteRow(index: number): void { + this.config.object[this.config.field]?.splice(index, 1); + this.table?.renderRows(); + this.stopEditing(); + } +} diff --git a/src/app/components/stix/dictionary-property/dictionary-property.component.html b/src/app/components/stix/dictionary-property/dictionary-property.component.html new file mode 100644 index 000000000..8a2b98f72 --- /dev/null +++ b/src/app/components/stix/dictionary-property/dictionary-property.component.html @@ -0,0 +1,11 @@ +
+ + + +
diff --git a/src/app/components/stix/dictionary-property/dictionary-property.component.scss b/src/app/components/stix/dictionary-property/dictionary-property.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/components/stix/dictionary-property/dictionary-property.component.ts b/src/app/components/stix/dictionary-property/dictionary-property.component.ts new file mode 100644 index 000000000..bf04b9ecf --- /dev/null +++ b/src/app/components/stix/dictionary-property/dictionary-property.component.ts @@ -0,0 +1,36 @@ +import { Component, Input } from '@angular/core'; +import { StixObject } from 'src/app/classes/stix'; + +@Component({ + selector: 'app-dictionary-property', + standalone: false, + templateUrl: './dictionary-property.component.html', + styleUrls: ['./dictionary-property.component.scss'], +}) +export class DictionaryPropertyComponent { + @Input() public config: DictionaryPropertyConfig; +} + +export interface DictionaryPropertyConfig { + /* What is the current mode? Default: 'view' */ + mode?: 'view' | 'edit' | 'diff'; + /* The fields to display as columns in the table */ + columns: DictionaryColumn[]; + /* The label for the table */ + label: string; + /* Label for the add button */ + buttonLabel?: string; + /* The tooltip for the add button/edit dialog */ + tooltip?: string; + /** The object to show the field of */ + object: StixObject | [StixObject, StixObject]; + field: string; +} + +interface DictionaryColumn { + name: string; + label?: string; // default name + editType: 'string' | 'description' | 'select' | 'autocomplete'; + required?: boolean; // default false + sort?: boolean; // default false +} diff --git a/src/app/components/stix/dictionary-property/dictionary-view/dictionary-view.component.html b/src/app/components/stix/dictionary-property/dictionary-view/dictionary-view.component.html new file mode 100644 index 000000000..47296bb4c --- /dev/null +++ b/src/app/components/stix/dictionary-property/dictionary-view/dictionary-view.component.html @@ -0,0 +1,20 @@ +
+
+ + + + + + + + +
+ {{ column.label || column.name }} + + {{ dataSource[idx][column.name] }} +
+
+ {{ config.label }} +
diff --git a/src/app/components/stix/dictionary-property/dictionary-view/dictionary-view.component.scss b/src/app/components/stix/dictionary-property/dictionary-view/dictionary-view.component.scss new file mode 100644 index 000000000..badd39b9a --- /dev/null +++ b/src/app/components/stix/dictionary-property/dictionary-view/dictionary-view.component.scss @@ -0,0 +1,9 @@ +@use '../../../../../style/globals'; + +.dictionary-view { + @extend .labelled-box; + + .content { + overflow-x: auto; + } +} diff --git a/src/app/components/stix/dictionary-property/dictionary-view/dictionary-view.component.ts b/src/app/components/stix/dictionary-property/dictionary-view/dictionary-view.component.ts new file mode 100644 index 000000000..41f3e2c60 --- /dev/null +++ b/src/app/components/stix/dictionary-property/dictionary-view/dictionary-view.component.ts @@ -0,0 +1,40 @@ +import { + Component, + Input, + ViewChild, + ViewEncapsulation, + OnInit, +} from '@angular/core'; +import { MatSort } from '@angular/material/sort'; +import { DictionaryPropertyConfig } from '../dictionary-property.component'; + +@Component({ + selector: 'app-dictionary-view', + standalone: false, + templateUrl: './dictionary-view.component.html', + styleUrls: ['./dictionary-view.component.scss'], + encapsulation: ViewEncapsulation.None, +}) +export class DictionaryViewComponent implements OnInit { + @Input() public config: DictionaryPropertyConfig; + @ViewChild(MatSort) sort: MatSort; + + public dataSource: any[] = []; + public displayedColumns: string[] = []; + + ngOnInit() { + this.dataSource = this.config.object[this.config.field]; + this.displayedColumns = this.config.columns.map(c => c.name); + + const sortColumn = this.config.columns.find(c => c.sort).name; + if (sortColumn) { + this.dataSource.sort((a, b) => { + const aValue = a[sortColumn] ?? ''; + const bValue = b[sortColumn] ?? ''; + return String(aValue).localeCompare(String(bValue), undefined, { + sensitivity: 'base', + }); + }); + } + } +} diff --git a/src/app/components/stix/external-references-property/external-references-diff/external-references-diff.component.spec.ts b/src/app/components/stix/external-references-property/external-references-diff/external-references-diff.component.spec.ts index 94ba4731a..7c2b490b6 100644 --- a/src/app/components/stix/external-references-property/external-references-diff/external-references-diff.component.spec.ts +++ b/src/app/components/stix/external-references-property/external-references-diff/external-references-diff.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ExternalReferencesDiffComponent } from './external-references-diff.component'; @@ -9,11 +10,19 @@ describe('ExternalReferencesDiffComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ExternalReferencesDiffComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); fixture = TestBed.createComponent(ExternalReferencesDiffComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { + mode: 'diff', + object: [ + { external_references: [] } as any, + { external_references: [] } as any, + ], + referencesField: 'external_references', + }; }); it('should create', () => { diff --git a/src/app/components/stix/external-references-property/external-references-property.component.spec.ts b/src/app/components/stix/external-references-property/external-references-property.component.spec.ts index 3708be928..d23914e90 100644 --- a/src/app/components/stix/external-references-property/external-references-property.component.spec.ts +++ b/src/app/components/stix/external-references-property/external-references-property.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ExternalReferencesPropertyComponent } from './external-references-property.component'; @@ -9,12 +10,18 @@ describe('ExternalReferencesPropertyComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ExternalReferencesPropertyComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(ExternalReferencesPropertyComponent); component = fixture.componentInstance; + component.config = { + mode: 'view', + object: {} as any, + referencesField: 'external_references', + }; fixture.detectChanges(); }); diff --git a/src/app/components/stix/external-references-property/external-references-view/external-references-view.component.spec.ts b/src/app/components/stix/external-references-property/external-references-view/external-references-view.component.spec.ts index 0a90d53db..cfb1f30e8 100644 --- a/src/app/components/stix/external-references-property/external-references-view/external-references-view.component.spec.ts +++ b/src/app/components/stix/external-references-property/external-references-view/external-references-view.component.spec.ts @@ -1,4 +1,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { provideRouter } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { of } from 'rxjs'; import { ExternalReferencesViewComponent } from './external-references-view.component'; @@ -9,13 +14,29 @@ describe('ExternalReferencesViewComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ExternalReferencesViewComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + provideHttpClient(), + provideRouter([]), + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + }, + }, + ], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(ExternalReferencesViewComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { + mode: 'view', + object: {} as any, + referencesField: 'external_references', + }; }); it('should create', () => { diff --git a/src/app/components/stix/identity-property/identity-property.component.spec.ts b/src/app/components/stix/identity-property/identity-property.component.spec.ts index 53c579e57..837abf70d 100644 --- a/src/app/components/stix/identity-property/identity-property.component.spec.ts +++ b/src/app/components/stix/identity-property/identity-property.component.spec.ts @@ -1,20 +1,42 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; import { IdentityPropertyComponent } from './identity-property.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createMockRestApiConnector, + createAsyncObservable, + createPaginatedResponse, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('IdentityPropertyComponent', () => { let component: IdentityPropertyComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getAllIdentities: () => createAsyncObservable(createPaginatedResponse()), + }); + await TestBed.configureTestingModule({ declarations: [IdentityPropertyComponent], + providers: [ + provideHttpClient(), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(IdentityPropertyComponent); component = fixture.componentInstance; + // Set required config input + component.config = { + object: {} as any, + field: 'identity', + }; fixture.detectChanges(); }); diff --git a/src/app/components/stix/list-property/list-diff/list-diff.component.spec.ts b/src/app/components/stix/list-property/list-diff/list-diff.component.spec.ts index 13c6d7148..7f05899cd 100644 --- a/src/app/components/stix/list-property/list-diff/list-diff.component.spec.ts +++ b/src/app/components/stix/list-property/list-diff/list-diff.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ListDiffComponent } from './list-diff.component'; @@ -9,11 +10,16 @@ describe('ListDiffComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ListDiffComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); fixture = TestBed.createComponent(ListDiffComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { + mode: 'diff', + object: [{} as any, {} as any], + field: 'platforms', + } as any; }); it('should create', () => { diff --git a/src/app/components/stix/list-property/list-edit/list-edit.component.spec.ts b/src/app/components/stix/list-property/list-edit/list-edit.component.spec.ts index 5ea8fa04a..67253eac1 100644 --- a/src/app/components/stix/list-property/list-edit/list-edit.component.spec.ts +++ b/src/app/components/stix/list-property/list-edit/list-edit.component.spec.ts @@ -1,21 +1,32 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ListEditComponent } from './list-edit.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { createMockRestApiConnector } from 'src/app/testing/mocks/rest-api-connector.mock'; describe('ListEditComponent', () => { let component: ListEditComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({}); + await TestBed.configureTestingModule({ declarations: [ListEditComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + provideHttpClient(), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(ListEditComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { mode: 'edit', object: {} as any, field: 'platforms' }; }); it('should create', () => { diff --git a/src/app/components/stix/list-property/list-property.component.spec.ts b/src/app/components/stix/list-property/list-property.component.spec.ts index a9d161f6a..9d45e9fef 100644 --- a/src/app/components/stix/list-property/list-property.component.spec.ts +++ b/src/app/components/stix/list-property/list-property.component.spec.ts @@ -1,20 +1,36 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; import { ListPropertyComponent } from './list-property.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { createMockRestApiConnector } from 'src/app/testing/mocks/rest-api-connector.mock'; describe('ListPropertyComponent', () => { let component: ListPropertyComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({}); + await TestBed.configureTestingModule({ declarations: [ListPropertyComponent], + providers: [ + provideHttpClient(), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(ListPropertyComponent); component = fixture.componentInstance; + // Set required config input + component.config = { + field: 'test', + object: {} as any, + }; fixture.detectChanges(); }); diff --git a/src/app/components/stix/list-property/list-view/list-view.component.spec.ts b/src/app/components/stix/list-property/list-view/list-view.component.spec.ts index 3c07cee4c..cf6726cbb 100644 --- a/src/app/components/stix/list-property/list-view/list-view.component.spec.ts +++ b/src/app/components/stix/list-property/list-view/list-view.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ListViewComponent } from './list-view.component'; @@ -9,12 +10,20 @@ describe('ListViewComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ListViewComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(ListViewComponent); component = fixture.componentInstance; + // Set required config input + component.config = { + mode: 'view', + object: { test: [] } as any, + field: 'test', + label: 'Test', + }; fixture.detectChanges(); }); diff --git a/src/app/components/stix/list-property/list-view/list-view.component.ts b/src/app/components/stix/list-property/list-view/list-view.component.ts index 357e951c8..fbc6b2fb6 100644 --- a/src/app/components/stix/list-property/list-view/list-view.component.ts +++ b/src/app/components/stix/list-property/list-view/list-view.component.ts @@ -1,7 +1,7 @@ import { Component, Input, ViewEncapsulation } from '@angular/core'; import { ListPropertyConfig } from '../list-property.component'; import { StixTypeToAttackType } from 'src/app/utils/type-mappings'; -import { RelatedRef } from 'src/app/classes/stix/stix-object'; +import { EmbeddedRelationship } from 'src/app/classes/stix/stix-object'; @Component({ selector: 'app-list-view', @@ -49,7 +49,7 @@ export class ListViewComponent { return arr; } - public getHTML(val: string | RelatedRef) { + public getHTML(val: string | EmbeddedRelationship) { if (this.config.objectProperty && typeof val === 'object') { if (this.showLink) { return `${val[this.config.objectProperty]}`; @@ -59,8 +59,9 @@ export class ListViewComponent { return val; } - public internalLink(item: RelatedRef): string { - const attackType = StixTypeToAttackType[item.type]; + public internalLink(item: EmbeddedRelationship): string { + const stixType = item.stixId.split('--')?.[0]; + const attackType = StixTypeToAttackType[stixType]; return `/${attackType}/${item.stixId}`; } } diff --git a/src/app/components/stix/log-source-reference-property/log-source-reference-dialog/log-source-reference-dialog.component.spec.ts b/src/app/components/stix/log-source-reference-property/log-source-reference-dialog/log-source-reference-dialog.component.spec.ts index aa5e3bf99..406f885a2 100644 --- a/src/app/components/stix/log-source-reference-property/log-source-reference-dialog/log-source-reference-dialog.component.spec.ts +++ b/src/app/components/stix/log-source-reference-property/log-source-reference-dialog/log-source-reference-dialog.component.spec.ts @@ -1,19 +1,45 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MatAutocompleteModule } from '@angular/material/autocomplete'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { LogSourceReferenceDialogComponent } from './log-source-reference-dialog.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createAsyncObservable, + createMockRestApiConnector, + createPaginatedResponse, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('LogSourceReferenceDialogComponent', () => { let component: LogSourceReferenceDialogComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getAllDataComponents: () => + createAsyncObservable(createPaginatedResponse([])), + getDataComponent: () => createAsyncObservable([]), + }); + await TestBed.configureTestingModule({ declarations: [LogSourceReferenceDialogComponent], + imports: [MatAutocompleteModule], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + { provide: MatDialogRef, useValue: {} }, + { + provide: MAT_DIALOG_DATA, + useValue: { object: { logSourceReferences: [] } }, + }, + ], }).compileComponents(); + }); + beforeEach(() => { fixture = TestBed.createComponent(LogSourceReferenceDialogComponent); component = fixture.componentInstance; - fixture.detectChanges(); }); it('should create', () => { diff --git a/src/app/components/stix/log-source-reference-property/log-source-reference-diff/log-source-reference-diff.component.spec.ts b/src/app/components/stix/log-source-reference-property/log-source-reference-diff/log-source-reference-diff.component.spec.ts index 11fe45bc9..e2e6c6e30 100644 --- a/src/app/components/stix/log-source-reference-property/log-source-reference-diff/log-source-reference-diff.component.spec.ts +++ b/src/app/components/stix/log-source-reference-property/log-source-reference-diff/log-source-reference-diff.component.spec.ts @@ -1,19 +1,37 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { LogSourceReferenceDiffComponent } from './log-source-reference-diff.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { createMockRestApiConnector } from 'src/app/testing/mocks/rest-api-connector.mock'; describe('LogSourceReferenceDiffComponent', () => { let component: LogSourceReferenceDiffComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({}); + await TestBed.configureTestingModule({ declarations: [LogSourceReferenceDiffComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], }).compileComponents(); + }); + beforeEach(() => { fixture = TestBed.createComponent(LogSourceReferenceDiffComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { + mode: 'diff', + object: [ + { logSourceReferences: [] } as any, + { logSourceReferences: [] } as any, + ], + field: 'logSourceReferences', + } as any; }); it('should create', () => { diff --git a/src/app/components/stix/log-source-reference-property/log-source-reference-edit/log-source-reference-edit.component.spec.ts b/src/app/components/stix/log-source-reference-property/log-source-reference-edit/log-source-reference-edit.component.spec.ts index 9cd78b2e4..d517351b9 100644 --- a/src/app/components/stix/log-source-reference-property/log-source-reference-edit/log-source-reference-edit.component.spec.ts +++ b/src/app/components/stix/log-source-reference-property/log-source-reference-edit/log-source-reference-edit.component.spec.ts @@ -1,18 +1,39 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { LogSourceReferenceEditComponent } from './log-source-reference-edit.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createAsyncObservable, + createMockRestApiConnector, + createPaginatedResponse, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('LogSourceReferenceEditComponent', () => { let component: LogSourceReferenceEditComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getAllDataComponents: () => + createAsyncObservable(createPaginatedResponse([])), + }); + await TestBed.configureTestingModule({ declarations: [LogSourceReferenceEditComponent], + providers: [ + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); fixture = TestBed.createComponent(LogSourceReferenceEditComponent); component = fixture.componentInstance; + component.config = { + mode: 'edit', + object: { logSourceReferences: [] } as any, + field: 'logSourceReferences', + } as any; fixture.detectChanges(); }); diff --git a/src/app/components/stix/log-source-reference-property/log-source-reference-property.component.spec.ts b/src/app/components/stix/log-source-reference-property/log-source-reference-property.component.spec.ts index 51c2a2138..c9b373693 100644 --- a/src/app/components/stix/log-source-reference-property/log-source-reference-property.component.spec.ts +++ b/src/app/components/stix/log-source-reference-property/log-source-reference-property.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { LogSourceReferencePropertyComponent } from './log-source-reference-property.component'; @@ -9,10 +10,15 @@ describe('LogSourceReferencePropertyComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [LogSourceReferencePropertyComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); fixture = TestBed.createComponent(LogSourceReferencePropertyComponent); component = fixture.componentInstance; + component.config = { + mode: 'view', + object: {} as any, + }; fixture.detectChanges(); }); diff --git a/src/app/components/stix/log-source-reference-property/log-source-reference-view/log-source-reference-view.component.spec.ts b/src/app/components/stix/log-source-reference-property/log-source-reference-view/log-source-reference-view.component.spec.ts index ea7fdaef4..bbcefb312 100644 --- a/src/app/components/stix/log-source-reference-property/log-source-reference-view/log-source-reference-view.component.spec.ts +++ b/src/app/components/stix/log-source-reference-property/log-source-reference-view/log-source-reference-view.component.spec.ts @@ -1,19 +1,34 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { LogSourceReferenceViewComponent } from './log-source-reference-view.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { createMockRestApiConnector } from 'src/app/testing/mocks/rest-api-connector.mock'; describe('LogSourceReferenceViewComponent', () => { let component: LogSourceReferenceViewComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({}); + await TestBed.configureTestingModule({ declarations: [LogSourceReferenceViewComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], }).compileComponents(); + }); + beforeEach(() => { fixture = TestBed.createComponent(LogSourceReferenceViewComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { + mode: 'view', + object: { logSourceReferences: [] } as any, + field: 'logSourceReferences', + } as any; }); it('should create', () => { diff --git a/src/app/components/stix/name-property/name-property.component.spec.ts b/src/app/components/stix/name-property/name-property.component.spec.ts index 98b87926a..5931f8e48 100644 --- a/src/app/components/stix/name-property/name-property.component.spec.ts +++ b/src/app/components/stix/name-property/name-property.component.spec.ts @@ -1,20 +1,34 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { NamePropertyComponent } from './name-property.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { createMockRestApiConnector } from 'src/app/testing/mocks/rest-api-connector.mock'; describe('NamePropertyComponent', () => { let component: NamePropertyComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({}); + await TestBed.configureTestingModule({ declarations: [NamePropertyComponent], + providers: [ + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(NamePropertyComponent); component = fixture.componentInstance; + // Set required config input + component.config = { + mode: 'view', + object: {} as any, + }; fixture.detectChanges(); }); diff --git a/src/app/components/stix/ordered-list-property/ordered-list-diff/ordered-list-diff.component.spec.ts b/src/app/components/stix/ordered-list-property/ordered-list-diff/ordered-list-diff.component.spec.ts index 3c4a05a8c..9bef3c4d9 100644 --- a/src/app/components/stix/ordered-list-property/ordered-list-diff/ordered-list-diff.component.spec.ts +++ b/src/app/components/stix/ordered-list-property/ordered-list-diff/ordered-list-diff.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { OrderedListDiffComponent } from './ordered-list-diff.component'; @@ -9,10 +10,20 @@ describe('OrderedListDiffComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [OrderedListDiffComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); fixture = TestBed.createComponent(OrderedListDiffComponent); component = fixture.componentInstance; + // Set required config input + component.config = { + mode: 'diff', + object: [{}, {}] as any, + objectOrderedListField: 'test', + field: 'test', + globalObjects: [], + label: 'Test', + }; fixture.detectChanges(); }); diff --git a/src/app/components/stix/ordered-list-property/ordered-list-edit/ordered-list-edit.component.spec.ts b/src/app/components/stix/ordered-list-property/ordered-list-edit/ordered-list-edit.component.spec.ts index 800107f89..eae8266cf 100644 --- a/src/app/components/stix/ordered-list-property/ordered-list-edit/ordered-list-edit.component.spec.ts +++ b/src/app/components/stix/ordered-list-property/ordered-list-edit/ordered-list-edit.component.spec.ts @@ -1,4 +1,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { DragDropModule } from '@angular/cdk/drag-drop'; import { OrderedListEditComponent } from './ordered-list-edit.component'; @@ -9,13 +12,22 @@ describe('OrderedListEditComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [OrderedListEditComponent], + imports: [DragDropModule], + schemas: [NO_ERRORS_SCHEMA], + providers: [provideHttpClient()], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(OrderedListEditComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { + mode: 'edit', + object: {} as any, + field: 'x_mitre_contributors', + objectOrderedListField: 'x_mitre_contributors', + globalObjects: [], + }; }); it('should create', () => { diff --git a/src/app/components/stix/ordered-list-property/ordered-list-property.component.spec.ts b/src/app/components/stix/ordered-list-property/ordered-list-property.component.spec.ts index 06fbf5680..a75d56f77 100644 --- a/src/app/components/stix/ordered-list-property/ordered-list-property.component.spec.ts +++ b/src/app/components/stix/ordered-list-property/ordered-list-property.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { OrderedListPropertyComponent } from './ordered-list-property.component'; @@ -9,12 +10,20 @@ describe('OrderedListPropertyComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [OrderedListPropertyComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(OrderedListPropertyComponent); component = fixture.componentInstance; + component.config = { + mode: 'view', + field: 'name', + object: {} as any, + objectOrderedListField: 'items', + globalObjects: [], + }; fixture.detectChanges(); }); diff --git a/src/app/components/stix/ordered-list-property/ordered-list-view/ordered-list-view.component.spec.ts b/src/app/components/stix/ordered-list-property/ordered-list-view/ordered-list-view.component.spec.ts index 50ed21dd1..4bbf78f7e 100644 --- a/src/app/components/stix/ordered-list-property/ordered-list-view/ordered-list-view.component.spec.ts +++ b/src/app/components/stix/ordered-list-property/ordered-list-view/ordered-list-view.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { OrderedListViewComponent } from './ordered-list-view.component'; @@ -9,12 +10,22 @@ describe('OrderedListViewComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [OrderedListViewComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(OrderedListViewComponent); component = fixture.componentInstance; + // Set required config input + component.config = { + mode: 'view', + object: {} as any, + objectOrderedListField: 'test', + objectField: 'test', + label: 'Test', + globalObjects: [], + }; fixture.detectChanges(); }); diff --git a/src/app/components/stix/statement-property/statement-diff/statement-diff.component.spec.ts b/src/app/components/stix/statement-property/statement-diff/statement-diff.component.spec.ts index de497d570..3039e9302 100644 --- a/src/app/components/stix/statement-property/statement-diff/statement-diff.component.spec.ts +++ b/src/app/components/stix/statement-property/statement-diff/statement-diff.component.spec.ts @@ -1,4 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { MtxPopoverModule } from '@ng-matero/extensions/popover'; import { StatementDiffComponent } from './statement-diff.component'; @@ -9,11 +11,19 @@ describe('StatementDiffComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [StatementDiffComponent], + imports: [MtxPopoverModule], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); + }); + beforeEach(() => { fixture = TestBed.createComponent(StatementDiffComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { + mode: 'diff', + object: [{} as any, {} as any], + field: 'statement', + } as any; }); it('should create', () => { diff --git a/src/app/components/stix/statement-property/statement-edit/statement-edit.component.spec.ts b/src/app/components/stix/statement-property/statement-edit/statement-edit.component.spec.ts index cefd1e3f2..06813d017 100644 --- a/src/app/components/stix/statement-property/statement-edit/statement-edit.component.spec.ts +++ b/src/app/components/stix/statement-property/statement-edit/statement-edit.component.spec.ts @@ -1,4 +1,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { MtxPopoverModule } from '@ng-matero/extensions/popover'; import { StatementEditComponent } from './statement-edit.component'; @@ -9,13 +12,20 @@ describe('StatementEditComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [StatementEditComponent], + imports: [MtxPopoverModule], + schemas: [NO_ERRORS_SCHEMA], + providers: [provideHttpClient()], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(StatementEditComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { + mode: 'edit', + object: {} as any, + field: 'statement', + } as any; }); it('should create', () => { diff --git a/src/app/components/stix/statement-property/statement-property.component.spec.ts b/src/app/components/stix/statement-property/statement-property.component.spec.ts index 80b37dd20..1a83977a1 100644 --- a/src/app/components/stix/statement-property/statement-property.component.spec.ts +++ b/src/app/components/stix/statement-property/statement-property.component.spec.ts @@ -1,14 +1,30 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { StatementPropertyComponent } from './statement-property.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createMockRestApiConnector, + createAsyncObservable, + createPaginatedResponse, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('StatementPropertyComponent', () => { let component: StatementPropertyComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getAllMarkingDefinitions: () => + createAsyncObservable(createPaginatedResponse([])), + }); + await TestBed.configureTestingModule({ declarations: [StatementPropertyComponent], + providers: [ + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); diff --git a/src/app/components/stix/statement-property/statement-view/statement-view.component.spec.ts b/src/app/components/stix/statement-property/statement-view/statement-view.component.spec.ts index c80134621..9bd573f9c 100644 --- a/src/app/components/stix/statement-property/statement-view/statement-view.component.spec.ts +++ b/src/app/components/stix/statement-property/statement-view/statement-view.component.spec.ts @@ -1,4 +1,6 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MtxPopoverModule } from '@ng-matero/extensions/popover'; import { StatementViewComponent } from './statement-view.component'; @@ -9,12 +11,20 @@ describe('StatementViewComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [StatementViewComponent], + imports: [MtxPopoverModule], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(StatementViewComponent); component = fixture.componentInstance; + // Set required config input + component.config = { + mode: 'view', + object: {} as any, + field: 'test', + } as any; fixture.detectChanges(); }); diff --git a/src/app/components/stix/stix-list/stix-list.component.html b/src/app/components/stix/stix-list/stix-list.component.html index 130ace12c..575129780 100644 --- a/src/app/components/stix/stix-list/stix-list.component.html +++ b/src/app/components/stix/stix-list/stix-list.component.html @@ -266,119 +266,84 @@
+ *ngIf="isCollectionType() && expandedElement === element">
- -
- -
-
- - - - - -
+ + + +
+ -
-
-
+ {{ route.label }} + +
+
+
- - Version - {{ - collection.version - }} - + + Version + {{ + collection.version + }} + - - Released - {{ - collection.release - }} - + + Released + {{ + collection.release + }} + - - Description - - {{ - collection.description - }} - - + + Description + + {{ + collection.description + }} + + - - -
-
+ + +
+ [class.expanded]=" + isCollectionType() && expandedElement === element + "> { let component: StixListComponent; let fixture: ComponentFixture; + const defaultConfig: StixListConfig = { + type: 'technique', + }; + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [StixListComponent], + providers: [provideHttpClient()], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(StixListComponent); component = fixture.componentInstance; + component.config = { ...defaultConfig }; fixture.detectChanges(); }); diff --git a/src/app/components/stix/stix-list/stix-list.component.ts b/src/app/components/stix/stix-list/stix-list.component.ts index 0e177ec96..54edba086 100644 --- a/src/app/components/stix/stix-list/stix-list.component.ts +++ b/src/app/components/stix/stix-list/stix-list.component.ts @@ -24,7 +24,10 @@ import { tap, } from 'rxjs/operators'; -import { RelatedRef, StixObject } from 'src/app/classes/stix/stix-object'; +import { + EmbeddedRelationship, + StixObject, +} from 'src/app/classes/stix/stix-object'; import { StixDialogComponent } from 'src/app/views/stix/stix-dialog/stix-dialog.component'; import { Paginated, @@ -32,7 +35,6 @@ import { } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; import { AuthenticationService } from 'src/app/services/connectors/authentication/authentication.service'; import { SidebarService } from 'src/app/services/sidebar/sidebar.service'; -import { MatSelect } from '@angular/material/select'; import { AddDialogComponent } from '../../add-dialog/add-dialog.component'; import { Collection } from 'src/app/classes/stix/collection'; import { logger } from 'src/app/utils/logger'; @@ -81,27 +83,21 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild('search') search: ElementRef; - @ViewChild(MatSelect) matSelect: MatSelect; // search query public searchQuery = ''; private searchSubscription: Subscription; // objects to render - public objects$: Observable; public data$: Observable>; public totalObjectCount = 0; - // view mode - public mode = 'cards'; - // options provided to the user for grouping and filtering public filterOptions: FilterGroup[] = []; public showControls = true; // current grouping and filtering selections public filter: string[] = []; - public groupBy: string[] = []; public userIdsUsedInSearch = []; // TABLE STUFF @@ -116,7 +112,7 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { // Selection stuff public selection: SelectionModel; - // all possible each type of filter/groupBy + // all possible each type of filter private platformSubscription: Subscription; private platformMap = new Map>(); private domains: FilterValue[] = [ @@ -225,7 +221,7 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { /** * Build the stix list table to display */ - private buildTable(): void { + protected buildTable(): void { // filter options this.filterOptions = []; if (!('showFilters' in this.config)) this.config.showFilters = true; @@ -234,15 +230,21 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { const sticky_allowed = !( this.config.rowAction && this.config.rowAction.position == 'start' ); + // Column presets take precedence + if (this.config.columnsPreset === 'id-name') { + this.addColumn('ID', 'attackID', 'plain', false); + this.addColumn('name', 'name', 'plain', sticky_allowed, ['name']); + this.tableDetail = []; + return; + } if ('type' in this.config) { // set columns according to type switch (this.config.type.replace(/_/g, '-')) { case 'collection': case 'collection-created': - this.addColumn('name', 'name', 'plain', sticky_allowed, ['name']); + this.addNameColumn(sticky_allowed); this.addColumn('latest version', 'version', 'version'); - this.addColumn('created', 'created', 'timestamp'); - this.addColumn('modified', 'modified', 'timestamp'); + this.addModifiedAndCreatedColumns(); this.tableDetail = [ { field: 'description', @@ -257,7 +259,7 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { ]; break; case 'collection-imported': - this.addColumn('name', 'name', 'plain', sticky_allowed, ['name']); + this.addNameColumn(sticky_allowed); this.addColumn('latest version', 'version', 'version'); this.addColumn('imported', 'imported', 'timestamp'); this.addColumn('modified', 'modified', 'timestamp'); @@ -276,48 +278,24 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { case 'mitigation': case 'tactic': case 'data-component': - this.addColumn('', 'workflow', 'icon'); - this.addColumn('', 'state', 'icon'); - this.addColumn('ID', 'attackID', 'plain', false); - this.addColumn('name', 'name', 'plain', sticky_allowed, ['name']); - this.addColumn('domain', 'domains', 'list'); + this.addWorkflowAndStateColumns(); + this.addIdAndNameColumns(sticky_allowed); + this.addDomainColumn(); this.addVersionsAndDatesColumns(); - this.tableDetail = [ - { - field: 'description', - display: 'descriptive', - }, - ]; break; case 'matrix': - this.addColumn('', 'workflow', 'icon'); - this.addColumn('', 'state', 'icon'); - this.addColumn('name', 'name', 'plain', sticky_allowed, ['name']); + this.addWorkflowAndStateColumns(); + this.addNameColumn(sticky_allowed); this.addVersionsAndDatesColumns(); - this.tableDetail = [ - { - field: 'description', - display: 'descriptive', - }, - ]; break; case 'detection-strategy': case 'campaign': - this.addColumn('', 'workflow', 'icon'); - this.addColumn('', 'state', 'icon'); - this.addColumn('ID', 'attackID', 'plain', false); - this.addColumn('name', 'name', 'plain', sticky_allowed, ['name']); + this.addWorkflowAndStateColumns(); + this.addIdAndNameColumns(sticky_allowed); this.addVersionsAndDatesColumns(); - this.tableDetail = [ - { - field: 'description', - display: 'descriptive', - }, - ]; break; case 'analytic': - this.addColumn('', 'workflow', 'icon'); - this.addColumn('', 'state', 'icon'); + this.addWorkflowAndStateColumns(); this.addColumn('ID', 'attackID', 'plain', false); this.addColumn( 'related detection strategy', @@ -327,151 +305,157 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { undefined, 'name' ); - this.addColumn('platform', 'platform', 'plain'); - this.addColumn('domain', 'domains', 'list'); + this.addPlatformsColumn('plain'); + this.addDomainColumn(); this.addVersionsAndDatesColumns(); - this.tableDetail = [ - { - field: 'description', - display: 'descriptive', - }, - ]; break; case 'group': - this.addColumn('', 'workflow', 'icon'); - this.addColumn('', 'state', 'icon'); - this.addColumn('ID', 'attackID', 'plain', false); - this.addColumn('name', 'name', 'plain', sticky_allowed, ['name']); + this.addWorkflowAndStateColumns(); + this.addIdAndNameColumns(sticky_allowed); this.addColumn('associated groups', 'aliases', 'list'); this.addVersionsAndDatesColumns(); - this.tableDetail = [ - { - field: 'description', - display: 'descriptive', - }, - ]; break; case 'software': - this.addColumn('', 'workflow', 'icon'); - this.addColumn('', 'state', 'icon'); - this.addColumn('ID', 'attackID', 'plain', false); - this.addColumn('name', 'name', 'plain', sticky_allowed, ['name']); + this.addWorkflowAndStateColumns(); + this.addIdAndNameColumns(sticky_allowed); this.addColumn('type', 'type', 'plain'); - this.addColumn('domain', 'domains', 'list'); + this.addDomainColumn(); this.addVersionsAndDatesColumns(); - this.tableDetail = [ - { - field: 'description', - display: 'descriptive', - }, - ]; break; case 'data-source': case 'technique': - this.addColumn('', 'workflow', 'icon'); - this.addColumn('', 'state', 'icon'); - this.addColumn('ID', 'attackID', 'plain', false); - this.addColumn('name', 'name', 'plain', sticky_allowed, ['name']); - this.addColumn('platforms', 'platforms', 'list'); - this.addColumn('domain', 'domains', 'list'); + this.addWorkflowAndStateColumns(); + this.addIdAndNameColumns(sticky_allowed); + this.addPlatformsColumn(); + this.addDomainColumn(); this.addVersionsAndDatesColumns(); - this.tableDetail = [ - { - field: 'description', - display: 'descriptive', - }, - ]; break; case 'asset': - this.addColumn('', 'workflow', 'icon'); - this.addColumn('', 'state', 'icon'); - this.addColumn('ID', 'attackID', 'plain', false); - this.addColumn('name', 'name', 'plain', sticky_allowed, ['name']); - this.addColumn('platforms', 'platforms', 'list'); + this.addWorkflowAndStateColumns(); + this.addIdAndNameColumns(sticky_allowed); + this.addPlatformsColumn(); this.addColumn('sectors', 'sectors', 'list'); this.addVersionsAndDatesColumns(); - this.tableDetail = [ - { - field: 'description', - display: 'descriptive', - }, - ]; break; case 'relationship': - this.addColumn('', 'state', 'icon'); - if ( - this.config.relationshipType && - this.config.relationshipType !== 'detects' - ) { + if (this.config.compactRelationshipColumns) { + // Only show source ID, type, target ID, and description this.addColumn('source', 'source_ID', 'plain'); + this.addColumn('type', 'relationship_type', 'plain', false, [ + 'text-deemphasis', + 'relationship-joiner', + ]); + this.addColumn('target', 'target_ID', 'plain'); + this.addColumn('description', 'description', 'descriptive', false); + this.addColumn('created', 'created', 'timestamp', false); + this.addColumn('modified', 'modified', 'timestamp', false); + } else { + this.addStateColumnOnly(); + if ( + this.config.relationshipType && + this.config.relationshipType !== 'detects' + ) { + this.addColumn('source', 'source_ID', 'plain'); + this.addColumn( + '', + 'source_name', + 'plain', + this.config.targetRef ? sticky_allowed : false, + ['relationship-name'] + ); + } else + this.addColumn( + 'source', + 'source_name', + 'plain', + this.config.targetRef ? sticky_allowed : false, + ['relationship-name'] + ); + this.addColumn('type', 'relationship_type', 'plain', false, [ + 'text-deemphasis', + 'relationship-joiner', + ]); + this.addColumn('target', 'target_ID', 'plain'); this.addColumn( '', - 'source_name', + 'target_name', 'plain', - this.config.targetRef ? sticky_allowed : false, + this.config.sourceRef ? sticky_allowed : false, ['relationship-name'] ); - } else - this.addColumn( - 'source', - 'source_name', - 'plain', - this.config.targetRef ? sticky_allowed : false, - ['relationship-name'] - ); - this.addColumn('type', 'relationship_type', 'plain', false, [ - 'text-deemphasis', - 'relationship-joiner', - ]); - this.addColumn('target', 'target_ID', 'plain'); - this.addColumn( - '', - 'target_name', - 'plain', - this.config.sourceRef ? sticky_allowed : false, - ['relationship-name'] - ); - if ( - !( - this.config.relationshipType && - this.config.relationshipType == 'subtechnique-of' + if ( + !( + this.config.relationshipType && + this.config.relationshipType == 'subtechnique-of' + ) ) - ) - this.addColumn('description', 'description', 'descriptive', false); + this.addColumn( + 'description', + 'description', + 'descriptive', + false + ); + } break; case 'marking-definition': this.addColumn('definition type', 'definition_type', 'plain'); this.addColumn('created', 'created', 'timestamp'); this.addColumn('definition', 'definition_string', 'descriptive'); - this.tableDetail = [ - { - field: 'definition_string', - display: 'descriptive', - }, - ]; break; case 'note': this.addColumn('title', 'title', 'plain'); this.addColumn('content', 'content', 'plain'); - this.addColumn('modified', 'modified', 'timestamp'); - this.addColumn('created', 'created', 'timestamp'); + this.addModifiedAndCreatedColumns(); break; default: this.addColumn('type', 'attackType', 'plain'); - this.addColumn('modified', 'modified', 'timestamp'); - this.addColumn('created', 'created', 'timestamp'); + this.addModifiedAndCreatedColumns(); } } else { - this.groupBy = ['type']; - this.addColumn('', 'state', 'icon'); + this.addStateColumnOnly(); this.addColumn('type', 'attackType', 'plain'); - this.addColumn('ID', 'attackID', 'plain', false); - this.addColumn('name', 'name', 'plain', true, ['name']); - this.addColumn('modified', 'modified', 'timestamp'); - this.addColumn('created', 'created', 'timestamp'); + this.addIdAndNameColumns(true); + this.addModifiedAndCreatedColumns(); } } + private addWorkflowAndStateColumns(): void { + this.addColumn('', 'workflow', 'icon'); + this.addColumn('', 'state', 'icon'); + } + + private addStateColumnOnly(): void { + this.addColumn('', 'state', 'icon'); + } + + private addIdAndNameColumns(stickyAllowed: boolean): void { + this.addColumn('ID', 'attackID', 'plain', false); + this.addColumn('name', 'name', 'plain', stickyAllowed, ['name']); + } + + private addNameColumn(stickyAllowed: boolean): void { + this.addColumn('name', 'name', 'plain', stickyAllowed, ['name']); + } + + private addDomainColumn(): void { + this.addColumn('domain', 'domains', 'list'); + } + + private addPlatformsColumn(type: column_types = 'list'): void { + this.addColumn('platforms', 'platforms', type); + } + + private addModifiedAndCreatedColumns(): void { + this.addColumn('modified', 'modified', 'timestamp'); + this.addColumn('created', 'created', 'timestamp'); + } + + private addVersionsAndDatesColumns() { + this.addColumn('version', 'version', 'version'); + this.addColumn('modified', 'modified', 'timestamp'); + this.addColumn('created', 'created', 'timestamp'); + } + /** * Set up controls, including control columns and filters */ @@ -524,7 +508,6 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { if (filterList.includes('workflow_status')) { this.filterOptions.push({ name: 'workflow status', - disabled: 'status' in this.config, values: this.statuses, }); } @@ -540,14 +523,12 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { if (filterList.includes('state')) { this.filterOptions.push({ name: 'state', - disabled: 'status' in this.config, values: this.states, }); } if (filterList.includes('state_exclusive')) { this.filterOptions.push({ name: 'state (exclusive)', - disabled: 'status' in this.config, values: this.statesExclusive, }); } @@ -567,7 +548,6 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { if (filterByDomain) { this.filterOptions.push({ name: 'domain', - disabled: 'status' in this.config, values: this.domains, }); } @@ -579,7 +559,6 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { if (platforms.length) { this.filterOptions.push({ name: 'platform', - disabled: 'status' in this.config, values: platforms, }); } @@ -604,21 +583,13 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { * @param {boolean} [sticky] is the column sticky? If true, the column will be static in the X scrolling of the view * @param {string[]} [classes] list of css classes to apply to the cell */ - private addColumn( + protected addColumn( label: string, field: string, - display: - | 'version' - | 'list' - | 'plain' - | 'timestamp' - | 'descriptive' - | 'relationship_name' - | 'icon' - | 'related_ref_list', + display: column_types, sticky?: boolean, classes?: string[], - relatedRefProperty?: keyof RelatedRef + relatedRefProperty?: keyof EmbeddedRelationship ) { this.tableColumns.push(field); this.tableColumns_settings.set(field, { @@ -630,15 +601,6 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { }); } - /** - * Add version, modified, and created columns to the table - */ - private addVersionsAndDatesColumns() { - this.addColumn('version', 'version', 'version'); - this.addColumn('modified', 'modified', 'timestamp'); - this.addColumn('created', 'created', 'timestamp'); - } - public openUserSelectModal(): void { const select = new SelectionModel(true); @@ -676,10 +638,9 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { * @param {StixObject} object of the row that was clicked */ public rowClick(element: StixObject) { - if (this.config.clickBehavior && this.config.clickBehavior == 'none') - return; - if (this.config.clickBehavior && this.config.clickBehavior == 'dialog') { - //open modal + if (this.config.clickBehavior == 'none') return; + + if (this.config.clickBehavior == 'dialog') { const prompt = this.dialog.open(StixDialogComponent, { data: { object: element, @@ -689,10 +650,11 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { maxHeight: '75vh', autoFocus: false, // prevents auto focus on toolbar buttons }); + const subscription = prompt.afterClosed().subscribe({ - next: result => { + next: () => { if (prompt.componentInstance.dirty) { - //re-fetch values since an edit occurred + // re-fetch values since an edit occurred this.applyControls(); this.refresh.emit(); } @@ -701,34 +663,26 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { subscription.unsubscribe(); }, }); - } else if ( - this.config.clickBehavior && - this.config.clickBehavior == 'linkToObjectPage' - ) { - this.onRowAction.emit(); // close any open dialogs - this.router.navigateByUrl( - '/' + element.attackType + '/' + element.stixID - ); - } else if ( - this.config.clickBehavior && - this.config.clickBehavior == 'linkToSourceRef' - ) { + return; + } + + if (this.config.clickBehavior == 'linkToSourceRef') { const source_ref = element['source_ref']; // Get type to navigate from source_ref const type = StixTypeToAttackType[source_ref.split('--')[0]]; this.router.navigateByUrl(`/${type}/${source_ref}`); - } else if ( - this.config.clickBehavior && - this.config.clickBehavior == 'linkToTargetRef' - ) { + return; + } + + if (this.config.clickBehavior == 'linkToTargetRef') { const target_ref = element['target_ref']; // Get type to navigate from target_ref const type = StixTypeToAttackType[target_ref.split('--')[0]]; this.router.navigateByUrl(`/${type}/${target_ref}`); - } else if ( - this.config.clickBehavior && - this.config.clickBehavior == 'linkToObjectRef' - ) { + return; + } + + if (this.config.clickBehavior == 'linkToObjectRef') { // technically a note can be linked to many objects, we will select the first object const object_ref = element['object_refs'][0]; // Get type to navigate from target_ref @@ -754,42 +708,53 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { } else { this.router.navigateByUrl(url); } - } else { - //expand + return; + } + + if (this.isCollectionType()) { this.expandedElement = this.expandedElement === element ? null : element; + return; } - } - // AUTHENTICATION FUNCTIONS + // by default, link to the object page + if ( + this.config.clickBehavior == 'linkToObjectPage' || + !this.config.clickBehavior + ) { + this.onRowAction.emit(); // close any open dialogs + this.router.navigateByUrl( + '/' + element.attackType + '/' + element.stixID + ); + return; + } + } - public getAccessibleRoutes(attackType: string, routes: any[]) { - return routes.filter( - route => this.canAccess(attackType, route) && this.canEdit(route) + public isCollectionType(): boolean { + return ['collection', 'collection-created', 'collection-imported'].includes( + this.config.type ); } - public routeTo(url, queryParams): void { - this.router.navigate([url], { queryParams: queryParams }); + // AUTHENTICATION FUNCTIONS + + public getAccessibleCollectionRoutes(collection) { + return collection.routes.filter(route => + this.canShowCollectionRoute(collection.attackType, route) + ); } - private canAccess(attackType: string, route: any) { - if ( - route.label && - route.label == 'edit' && - !this.authenticationService.canEdit(attackType) - ) { - // user not authorized - return false; + private canShowCollectionRoute(attackType: string, route): boolean { + if (route.label == 'edit') { + return ( + this.authenticationService.canEdit(attackType) && + !this.config.uneditableObject + ); } - // user authorized return true; } - private canEdit(route: any) { - if (route.label && route.label == 'edit' && this.config.uneditableObject) { - return false; - } - return true; + public routeTo(url, queryParams): void { + this.router.navigate([url], { queryParams: queryParams }); } /** @@ -908,11 +873,135 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { } } + private hasLocalStixObjects(): boolean { + return ( + 'stixObjects' in this.config && this.config.stixObjects !== undefined + ); + } + /** * Apply all controls and fetch objects from the back-end if configured */ - public applyControls() { - const { + public applyControls(): void { + const filterStates = this.getFilterObjectStates(); + + if (this.hasLocalStixObjects()) { + this.applyControlsToLocalData(filterStates); + } else { + this.applyControlsToBackendData(filterStates); + } + } + + private applyControlsToLocalData(filterStates): void { + if (this.config.stixObjects instanceof Observable) { + // pull objects out of observable + return; + } + // filter on STIX objects specified in the config + let filtered = this.config.stixObjects; + filtered = this.filterLocalObjects(filtered, filterStates); + filtered = this.sortObjects(filtered); + + if (this.paginator) this.totalObjectCount = filtered.length; + + // filter to only ones within the correct index range + const { startIndex, endIndex } = this.getPaginationRange(); + const paged = filtered.slice(startIndex, endIndex); + + this.data$ = of({ + data: paged, + pagination: { + total: this.config.stixObjects.length, + offset: startIndex, + limit: this.paginator ? this.paginator.pageSize : 0, + }, + }); + + this.emitDetectsHasData(paged.length > 0); + } + + private applyControlsToBackendData(filterStates): void { + // fetch objects from backend + const limit = this.paginator ? this.paginator.pageSize : 10; + const offset = this.paginator ? this.paginator.pageIndex * limit : 0; + + const options = { + limit: limit, + offset: offset, + excludeIDs: this.config.excludeIDs, + search: this.searchQuery, + state: filterStates.state, + includeRevoked: filterStates.revoked, + includeDeprecated: filterStates.deprecated, + platforms: filterStates.platforms, + domains: filterStates.domains, + lastUpdatedBy: this.userIdsUsedInSearch, + }; + if (this.config.type == 'software') + this.data$ = this.restAPIConnectorService.getAllSoftware(options); + else if (this.config.type == 'campaign') + this.data$ = this.restAPIConnectorService.getAllCampaigns(options); + else if (this.config.type == 'group') + this.data$ = this.restAPIConnectorService.getAllGroups(options); + else if (this.config.type == 'matrix') + this.data$ = this.restAPIConnectorService.getAllMatrices(options); + else if (this.config.type == 'mitigation') + this.data$ = this.restAPIConnectorService.getAllMitigations(options); + else if (this.config.type == 'tactic') + this.data$ = this.restAPIConnectorService.getAllTactics(options); + else if (this.config.type == 'technique') + this.data$ = this.restAPIConnectorService.getAllTechniques(options); + else if (this.config.type.includes('collection')) + this.data$ = this.restAPIConnectorService.getAllCollections({ + search: this.searchQuery, + versions: 'all', + }); + else if (this.config.type == 'relationship') + this.data$ = this.restAPIConnectorService.getRelatedTo({ + sourceRef: this.config.sourceRef, + targetRef: this.config.targetRef, + sourceType: this.config.sourceType, + targetType: this.config.targetType, + relationshipType: this.config.relationshipType, + excludeSourceRefs: this.config.excludeSourceRefs, + excludeTargetRefs: this.config.excludeTargetRefs, + limit: limit, + offset: offset, + includeDeprecated: filterStates.deprecated, + }); + else if (this.config.type == 'detection-strategy') + this.data$ = + this.restAPIConnectorService.getAllDetectionStrategies(options); + else if (this.config.type == 'analytic') + this.data$ = this.restAPIConnectorService.getAllAnalytics({ + ...options, + includeRefs: true, + }); + else if (this.config.type == 'data-source') + this.data$ = this.restAPIConnectorService.getAllDataSources(options); + else if (this.config.type == 'data-component') + this.data$ = this.restAPIConnectorService.getAllDataComponents(options); + else if (this.config.type == 'asset') + this.data$ = this.restAPIConnectorService.getAllAssets(options); + else if (this.config.type == 'marking-definition') + this.data$ = + this.restAPIConnectorService.getAllMarkingDefinitions(options); + else if (this.config.type == 'note') + this.data$ = this.restAPIConnectorService.getAllNotes(options); + const subscription = this.data$.subscribe({ + next: data => { + this.totalObjectCount = data.pagination.total; + this.emitDetectsHasData(data.data.length > 0); + }, + complete: () => { + if (subscription) subscription.unsubscribe(); + }, + }); + } + + private filterLocalObjects( + objects: StixObject[], + { deprecated, revoked, state, @@ -920,186 +1009,96 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { domains, exclusiveDeprecated, exclusiveRevoked, - } = this.getFilterObjectStates(); - if ('stixObjects' in this.config && this.config.stixObjects !== undefined) { - if (this.config.stixObjects instanceof Observable) { - // pull objects out of observable - } else { - // filter on STIX objects specified in the config - let filtered = this.config.stixObjects; - - //filter by domains - if (Array.isArray(domains) && domains.length > 0) { - filtered = filtered.filter((obj: any) => - obj.domains.some((object_domain: any) => - domains.includes(object_domain) - ) - ); - } + }: ReturnType + ): StixObject[] { + let filtered = objects; + //filter by domains + if (Array.isArray(domains) && domains.length > 0) { + filtered = filtered.filter((obj: any) => + obj.domains.some((object_domain: any) => + domains.includes(object_domain) + ) + ); + } - //filter by platforms - if (Array.isArray(platforms) && platforms.length > 0) { - filtered = filtered.filter((obj: any) => - obj.platforms.some((object_platform: any) => - platforms.includes(object_platform) - ) - ); - } + //filter by platforms + if (Array.isArray(platforms) && platforms.length > 0) { + filtered = filtered.filter((obj: any) => + obj.platforms.some((object_platform: any) => + platforms.includes(object_platform) + ) + ); + } - // filter by workflow status - if (state) { - filtered = filtered.filter( - (obj: any) => obj.workflow && obj.workflow.state == state - ); - } + // filter by workflow status + if (state) { + filtered = filtered.filter( + (obj: any) => obj.workflow && obj.workflow.state == state + ); + } - // filter by deprecation status - if (exclusiveDeprecated) { - filtered = filtered.filter((obj: any) => obj.deprecated); - } else if (deprecated || this.config.includeDeprecatedObjects) { - filtered = filtered.filter((obj: any) => obj || obj.deprecated); - } else { - filtered = filtered.filter((obj: any) => !obj.deprecated); - } + // filter by deprecation status + if (exclusiveDeprecated) { + filtered = filtered.filter((obj: any) => obj.deprecated); + } else if (deprecated || this.config.includeDeprecatedObjects) { + filtered = filtered.filter((obj: any) => obj || obj.deprecated); + } else { + filtered = filtered.filter((obj: any) => !obj.deprecated); + } - // filter by revocation status - if (exclusiveRevoked) { - filtered = filtered.filter((obj: any) => obj.revoked); - } + // filter by revocation status + if (exclusiveRevoked) { + filtered = filtered.filter((obj: any) => obj.revoked); + } - // filter by users - if ( - Array.isArray(this.userIdsUsedInSearch) && - this.userIdsUsedInSearch.length > 0 - ) { - filtered = filtered.filter( - (obj: any) => - obj.workflow && - this.userIdsUsedInSearch.includes( - obj.workflow.created_by_user_account - ) - ); - } + // filter by users + if ( + Array.isArray(this.userIdsUsedInSearch) && + this.userIdsUsedInSearch.length > 0 + ) { + filtered = filtered.filter( + (obj: any) => + obj.workflow && + this.userIdsUsedInSearch.includes( + obj.workflow.created_by_user_account + ) + ); + } - // filter to objects matching searchString - filtered = this.filterObjects(this.searchQuery, filtered); - // sort - filtered = filtered.sort((a, b) => { - const x = a as any; - const y = b as any; - return x.hasOwnProperty('name') && y.hasOwnProperty('name') - ? x.name.localeCompare(y.name) - : x.stixID.localeCompare(y.stixID); - }); - if (this.paginator) this.totalObjectCount = filtered.length; - - // filter to only ones within the correct index range - const startIndex = this.paginator - ? this.paginator.pageIndex * this.paginator.pageSize - : 0; - const endIndex = this.paginator - ? startIndex + this.paginator.pageSize - : 10; - filtered = filtered.slice(startIndex, endIndex); - this.data$ = of({ - data: filtered, - pagination: { - total: this.config.stixObjects.length, - offset: startIndex, - limit: this.paginator ? this.paginator.pageSize : 0, - }, - }); - // used to conditionally hide data component relationships with techniques - if ( - this.config.type === 'relationship' && - this.config.relationshipType === 'detects' - ) { - this.detectsHasData.emit(filtered.length > 0); - } - } - } else { - // fetch objects from backend - const limit = this.paginator ? this.paginator.pageSize : 10; - const offset = this.paginator ? this.paginator.pageIndex * limit : 0; + // filter to objects matching searchString + filtered = this.filterObjects(this.searchQuery, filtered); + return filtered; + } - const options = { - limit: limit, - offset: offset, - excludeIDs: this.config.excludeIDs, - search: this.searchQuery, - state: state, - includeRevoked: revoked, - includeDeprecated: deprecated, - platforms: platforms, - domains: domains, - lastUpdatedBy: this.userIdsUsedInSearch, - }; - if (this.config.type == 'software') - this.data$ = this.restAPIConnectorService.getAllSoftware(options); - else if (this.config.type == 'campaign') - this.data$ = this.restAPIConnectorService.getAllCampaigns(options); - else if (this.config.type == 'group') - this.data$ = this.restAPIConnectorService.getAllGroups(options); - else if (this.config.type == 'matrix') - this.data$ = this.restAPIConnectorService.getAllMatrices(options); - else if (this.config.type == 'mitigation') - this.data$ = this.restAPIConnectorService.getAllMitigations(options); - else if (this.config.type == 'tactic') - this.data$ = this.restAPIConnectorService.getAllTactics(options); - else if (this.config.type == 'technique') - this.data$ = this.restAPIConnectorService.getAllTechniques(options); - else if (this.config.type.includes('collection')) - this.data$ = this.restAPIConnectorService.getAllCollections({ - search: this.searchQuery, - versions: 'all', - }); - else if (this.config.type == 'relationship') - this.data$ = this.restAPIConnectorService.getRelatedTo({ - sourceRef: this.config.sourceRef, - targetRef: this.config.targetRef, - sourceType: this.config.sourceType, - targetType: this.config.targetType, - relationshipType: this.config.relationshipType, - excludeSourceRefs: this.config.excludeSourceRefs, - excludeTargetRefs: this.config.excludeTargetRefs, - limit: limit, - offset: offset, - includeDeprecated: deprecated, - }); - else if (this.config.type == 'detection-strategy') - this.data$ = - this.restAPIConnectorService.getAllDetectionStrategies(options); - else if (this.config.type == 'analytic') - this.data$ = this.restAPIConnectorService.getAllAnalytics({ - ...options, - includeRefs: true, - }); - else if (this.config.type == 'data-source') - this.data$ = this.restAPIConnectorService.getAllDataSources(options); - else if (this.config.type == 'data-component') - this.data$ = this.restAPIConnectorService.getAllDataComponents(options); - else if (this.config.type == 'asset') - this.data$ = this.restAPIConnectorService.getAllAssets(options); - else if (this.config.type == 'marking-definition') - this.data$ = - this.restAPIConnectorService.getAllMarkingDefinitions(options); - else if (this.config.type == 'note') - this.data$ = this.restAPIConnectorService.getAllNotes(options); - const subscription = this.data$.subscribe({ - next: data => { - this.totalObjectCount = data.pagination.total; - // used to conditionally hide data component relationships with techniques - if ( - this.config.type === 'relationship' && - this.config.relationshipType === 'detects' - ) { - this.detectsHasData.emit(data.data.length > 0); - } - }, - complete: () => { - if (subscription) subscription.unsubscribe(); - }, - }); + private sortObjects(objects: StixObject[]): StixObject[] { + return [...objects].sort((a, b) => { + const x = a as any; + const y = b as any; + return x.hasOwnProperty('name') && y.hasOwnProperty('name') + ? x.name.localeCompare(y.name) + : x.stixID.localeCompare(y.stixID); + }); + } + + private getPaginationRange(): { + startIndex: number; + endIndex: number; + } { + const startIndex = this.paginator + ? this.paginator.pageIndex * this.paginator.pageSize + : 0; + const endIndex = this.paginator ? startIndex + this.paginator.pageSize : 10; + + return { startIndex, endIndex }; + } + + private emitDetectsHasData(hasData: boolean) { + // used to conditionally hide data component relationships with techniques + if ( + this.config.type === 'relationship' && + this.config.relationshipType === 'detects' + ) { + this.detectsHasData.emit(hasData); } } @@ -1204,7 +1203,16 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { } type selection_types = 'one' | 'many' | 'disabled'; -type filter_types = 'state' | 'workflow_status'; +type filter_types = 'state' | 'workflow_status' | 'state_exclusive'; +type column_types = + | 'version' + | 'list' + | 'plain' + | 'timestamp' + | 'descriptive' + | 'relationship_name' + | 'icon' + | 'related_ref_list'; export interface StixListConfig { /* if specified, shows the given STIX objects in the table instead of loading from the back-end based on other configurations. */ stixObjects?: Observable | StixObject[]; @@ -1220,8 +1228,6 @@ export interface StixListConfig { /** force the list to show only this type */ type?: AttackType | 'collection-created' | 'collection-imported'; - /** force the list to show only objects matching this query */ - query?: any; /** can the user select in this list? allowed options: * "one": user can select a single element at a time @@ -1240,6 +1246,8 @@ export interface StixListConfig { showFilters?: boolean; /** default true, if false hides all search/filter/control options */ showControls?: boolean; + /** Optional preset to override default columns */ + columnsPreset?: 'id-name'; /** display the 'show deprecated' filter, default false * this may be relevant when displaying a list of embedded relationships, where * the list of STIX objects is provided in the 'stixObjects' configuration @@ -1295,6 +1303,11 @@ export interface StixListConfig { * Map of collections by stixID */ collectionMap?: Map; + + /** + * If true, collapse relationship columns to source, type, target, description only + */ + compactRelationshipColumns?: boolean; } export interface FilterValue { diff --git a/src/app/components/stix/stixid-property/stixid-property.component.spec.ts b/src/app/components/stix/stixid-property/stixid-property.component.spec.ts index 0026b45c2..74949ce3e 100644 --- a/src/app/components/stix/stixid-property/stixid-property.component.spec.ts +++ b/src/app/components/stix/stixid-property/stixid-property.component.spec.ts @@ -1,4 +1,9 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { MatIconModule } from '@angular/material/icon'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { StixObject } from 'src/app/classes/stix/stix-object'; import { StixIDPropertyComponent } from './stixid-property.component'; @@ -9,12 +14,18 @@ describe('StixidPropertyComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [StixIDPropertyComponent], + imports: [MatSnackBarModule, MatIconModule, NoopAnimationsModule], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(StixIDPropertyComponent); component = fixture.componentInstance; + component.config = { + mode: 'view', + object: { stixID: 'test-stix-id' } as StixObject, + }; fixture.detectChanges(); }); diff --git a/src/app/components/stix/string-property/string-property.component.spec.ts b/src/app/components/stix/string-property/string-property.component.spec.ts index 8186708f9..4cece281a 100644 --- a/src/app/components/stix/string-property/string-property.component.spec.ts +++ b/src/app/components/stix/string-property/string-property.component.spec.ts @@ -1,20 +1,35 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { StixObject } from 'src/app/classes/stix/stix-object'; import { StringPropertyComponent } from './string-property.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { createMockRestApiConnector } from 'src/app/testing/mocks/rest-api-connector.mock'; describe('StringPropertyComponent', () => { let component: StringPropertyComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({}); + await TestBed.configureTestingModule({ declarations: [StringPropertyComponent], + providers: [ + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(StringPropertyComponent); component = fixture.componentInstance; + component.config = { + mode: 'view', + object: { stixID: 'test-stix-id' } as StixObject, + field: 'name', + }; fixture.detectChanges(); }); diff --git a/src/app/components/stix/subtype-property/subtype-dialog/subtype-dialog.component.spec.ts b/src/app/components/stix/subtype-property/subtype-dialog/subtype-dialog.component.spec.ts index f44cccc23..295d609bc 100644 --- a/src/app/components/stix/subtype-property/subtype-dialog/subtype-dialog.component.spec.ts +++ b/src/app/components/stix/subtype-property/subtype-dialog/subtype-dialog.component.spec.ts @@ -1,21 +1,57 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { provideRouter } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { of } from 'rxjs'; import { SubtypeDialogComponent } from './subtype-dialog.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createAsyncObservable, + createMockRestApiConnector, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('SubtypeDialogComponent', () => { let component: SubtypeDialogComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getAllAllowedValues: () => createAsyncObservable([]), + }); + await TestBed.configureTestingModule({ declarations: [SubtypeDialogComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + provideHttpClient(), + provideRouter([]), + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + }, + }, + { provide: MatDialogRef, useValue: {} }, + { + provide: MAT_DIALOG_DATA, + useValue: { + subtypeFields: [], + object: { attackType: 'test-type' }, + field: 'testField', + }, + }, + ], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(SubtypeDialogComponent); component = fixture.componentInstance; - fixture.detectChanges(); }); it('should create', () => { diff --git a/src/app/components/stix/subtype-property/subtype-diff/subtype-diff.component.spec.ts b/src/app/components/stix/subtype-property/subtype-diff/subtype-diff.component.spec.ts index 976f93724..5c8536b09 100644 --- a/src/app/components/stix/subtype-property/subtype-diff/subtype-diff.component.spec.ts +++ b/src/app/components/stix/subtype-property/subtype-diff/subtype-diff.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { SubtypeDiffComponent } from './subtype-diff.component'; @@ -9,11 +10,18 @@ describe('SubtypeDiffComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [SubtypeDiffComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); + }); + beforeEach(() => { fixture = TestBed.createComponent(SubtypeDiffComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { + mode: 'diff', + object: [{}, {}], + field: 'x_mitre_attack_spec_version', + }; }); it('should create', () => { diff --git a/src/app/components/stix/subtype-property/subtype-edit/subtype-edit.component.spec.ts b/src/app/components/stix/subtype-property/subtype-edit/subtype-edit.component.spec.ts index 6488a9abf..446d6f034 100644 --- a/src/app/components/stix/subtype-property/subtype-edit/subtype-edit.component.spec.ts +++ b/src/app/components/stix/subtype-property/subtype-edit/subtype-edit.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { SubtypeEditComponent } from './subtype-edit.component'; @@ -9,13 +10,19 @@ describe('SubtypeEditComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [SubtypeEditComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(SubtypeEditComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { + mode: 'edit', + object: {}, + field: 'x_mitre_platforms', + subtypeFields: [{ key: 'name' }], + }; }); it('should create', () => { diff --git a/src/app/components/stix/subtype-property/subtype-property.component.spec.ts b/src/app/components/stix/subtype-property/subtype-property.component.spec.ts index df7849ad1..d8039ca54 100644 --- a/src/app/components/stix/subtype-property/subtype-property.component.spec.ts +++ b/src/app/components/stix/subtype-property/subtype-property.component.spec.ts @@ -1,6 +1,8 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { SubtypePropertyComponent } from './subtype-property.component'; +import { StixObject } from 'src/app/classes/stix'; describe('SubtypePropertyComponent', () => { let component: SubtypePropertyComponent; @@ -9,12 +11,22 @@ describe('SubtypePropertyComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [SubtypePropertyComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(SubtypePropertyComponent); component = fixture.componentInstance; + // Set required config input + component.config = { + mode: 'view', + object: {} as StixObject, + field: 'test', + label: 'Test', + subtypeFields: [], + tooltip: 'Test tooltip', + }; fixture.detectChanges(); }); diff --git a/src/app/components/stix/subtype-property/subtype-view/subtype-view.component.html b/src/app/components/stix/subtype-property/subtype-view/subtype-view.component.html index 89c2f96c9..ff5680bc3 100644 --- a/src/app/components/stix/subtype-property/subtype-view/subtype-view.component.html +++ b/src/app/components/stix/subtype-property/subtype-view/subtype-view.component.html @@ -5,7 +5,7 @@ *ngIf="detailTable.length" mat-table [dataSource]="detailTable" - class="subtype-table"> + class="property-table"> diff --git a/src/app/components/stix/subtype-property/subtype-view/subtype-view.component.scss b/src/app/components/stix/subtype-property/subtype-view/subtype-view.component.scss index 418fad1cd..00d5875a2 100644 --- a/src/app/components/stix/subtype-property/subtype-view/subtype-view.component.scss +++ b/src/app/components/stix/subtype-property/subtype-view/subtype-view.component.scss @@ -1,5 +1,4 @@ @use '../../../../../style/globals'; -@use '../../../../../style/colors'; .subtype-view { @extend .labelled-box; @@ -7,29 +6,7 @@ .content { overflow-x: auto; } - .subtype-table { - width: 100%; - padding: 8px; - .light & { - background-color: colors.color(light); - } - .dark & { - background-color: colors.color(dark); - } - tr.mat-mdc-header-row { - height: 25px; - } - .mat-mdc-cell, - .mat-mdc-header-cell { - padding: 0 10px; - .light & { - border-color: colors.border-color(light); - } - .dark & { - border-color: colors.border-color(dark); - } - } - } + .subtype-markdown { p { padding: 0; diff --git a/src/app/components/stix/subtype-property/subtype-view/subtype-view.component.spec.ts b/src/app/components/stix/subtype-property/subtype-view/subtype-view.component.spec.ts index f3053202a..c33f999ea 100644 --- a/src/app/components/stix/subtype-property/subtype-view/subtype-view.component.spec.ts +++ b/src/app/components/stix/subtype-property/subtype-view/subtype-view.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { SubtypeViewComponent } from './subtype-view.component'; @@ -9,12 +10,22 @@ describe('SubtypeViewComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [SubtypeViewComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(SubtypeViewComponent); component = fixture.componentInstance; + // Set required config input + component.config = { + mode: 'view', + object: { test: {} } as any, + field: 'test', + label: 'Test', + subtypeFields: [], + tooltip: 'Test', + }; fixture.detectChanges(); }); diff --git a/src/app/components/stix/timestamp-property/timestamp-property.component.spec.ts b/src/app/components/stix/timestamp-property/timestamp-property.component.spec.ts index 07da9c4c4..e308d6035 100644 --- a/src/app/components/stix/timestamp-property/timestamp-property.component.spec.ts +++ b/src/app/components/stix/timestamp-property/timestamp-property.component.spec.ts @@ -1,6 +1,8 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { TimestampPropertyComponent } from './timestamp-property.component'; +import { StixObject } from 'src/app/classes/stix/stix-object'; describe('TimestampPropertyComponent', () => { let component: TimestampPropertyComponent; @@ -9,12 +11,19 @@ describe('TimestampPropertyComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [TimestampPropertyComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(TimestampPropertyComponent); component = fixture.componentInstance; + // Set required config input + component.config = { + mode: 'view', + object: {} as StixObject, + field: 'created', + }; fixture.detectChanges(); }); diff --git a/src/app/components/stix/timestamp-property/timestamp-view/timestamp-view.component.spec.ts b/src/app/components/stix/timestamp-property/timestamp-view/timestamp-view.component.spec.ts index 7ab2cd898..869ee4e42 100644 --- a/src/app/components/stix/timestamp-property/timestamp-view/timestamp-view.component.spec.ts +++ b/src/app/components/stix/timestamp-property/timestamp-view/timestamp-view.component.spec.ts @@ -1,21 +1,30 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TimestampViewComponent } from './timestamp-view.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { createMockRestApiConnector } from 'src/app/testing/mocks/rest-api-connector.mock'; describe('TimestampViewComponent', () => { let component: TimestampViewComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({}); + await TestBed.configureTestingModule({ declarations: [TimestampViewComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(TimestampViewComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { mode: 'view', object: {} as any, field: 'created' }; }); it('should create', () => { diff --git a/src/app/components/stix/tlp-property/tlp-diff/tlp-diff.component.spec.ts b/src/app/components/stix/tlp-property/tlp-diff/tlp-diff.component.spec.ts index 49d627122..599087a9d 100644 --- a/src/app/components/stix/tlp-property/tlp-diff/tlp-diff.component.spec.ts +++ b/src/app/components/stix/tlp-property/tlp-diff/tlp-diff.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { TlpDiffComponent } from './tlp-diff.component'; @@ -9,10 +10,16 @@ describe('TlpDiffComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [TlpDiffComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); fixture = TestBed.createComponent(TlpDiffComponent); component = fixture.componentInstance; + // Set required config input + component.config = { + mode: 'diff', + object: [{} as any, {} as any], + }; fixture.detectChanges(); }); diff --git a/src/app/components/stix/tlp-property/tlp-edit/tlp-edit.component.spec.ts b/src/app/components/stix/tlp-property/tlp-edit/tlp-edit.component.spec.ts index 9391a4d0d..ffa372f46 100644 --- a/src/app/components/stix/tlp-property/tlp-edit/tlp-edit.component.spec.ts +++ b/src/app/components/stix/tlp-property/tlp-edit/tlp-edit.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { TlpEditComponent } from './tlp-edit.component'; @@ -9,6 +10,7 @@ describe('TlpEditComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [TlpEditComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); diff --git a/src/app/components/stix/tlp-property/tlp-property.component.spec.ts b/src/app/components/stix/tlp-property/tlp-property.component.spec.ts index c400ea245..816030df6 100644 --- a/src/app/components/stix/tlp-property/tlp-property.component.spec.ts +++ b/src/app/components/stix/tlp-property/tlp-property.component.spec.ts @@ -1,20 +1,41 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { StixObject } from 'src/app/classes/stix/stix-object'; import { TlpPropertyComponent } from './tlp-property.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createMockRestApiConnector, + createAsyncObservable, + createPaginatedResponse, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('TlpPropertyComponent', () => { let component: TlpPropertyComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getAllMarkingDefinitions: () => + createAsyncObservable(createPaginatedResponse([])), + }); + await TestBed.configureTestingModule({ declarations: [TlpPropertyComponent], + providers: [ + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(TlpPropertyComponent); component = fixture.componentInstance; + component.config = { + mode: 'view', + object: { stixID: 'test-stix-id' } as StixObject, + }; fixture.detectChanges(); }); diff --git a/src/app/components/stix/tlp-property/tlp-view/tlp-view.component.spec.ts b/src/app/components/stix/tlp-property/tlp-view/tlp-view.component.spec.ts index 292a6f6ac..a1d11632b 100644 --- a/src/app/components/stix/tlp-property/tlp-view/tlp-view.component.spec.ts +++ b/src/app/components/stix/tlp-property/tlp-view/tlp-view.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { TlpViewComponent } from './tlp-view.component'; @@ -9,6 +10,7 @@ describe('TlpViewComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [TlpViewComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); diff --git a/src/app/components/stix/version-property/version-diff/version-diff.component.spec.ts b/src/app/components/stix/version-property/version-diff/version-diff.component.spec.ts index 8b06f6db2..96c30b762 100644 --- a/src/app/components/stix/version-property/version-diff/version-diff.component.spec.ts +++ b/src/app/components/stix/version-property/version-diff/version-diff.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { VersionDiffComponent } from './version-diff.component'; @@ -9,10 +10,16 @@ describe('VersionDiffComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [VersionDiffComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); fixture = TestBed.createComponent(VersionDiffComponent); component = fixture.componentInstance; + // Set required config input + component.config = { + mode: 'diff', + object: [{} as any, {} as any], + }; fixture.detectChanges(); }); diff --git a/src/app/components/stix/version-property/version-edit/version-edit.component.spec.ts b/src/app/components/stix/version-property/version-edit/version-edit.component.spec.ts index b6ad8de63..c63435896 100644 --- a/src/app/components/stix/version-property/version-edit/version-edit.component.spec.ts +++ b/src/app/components/stix/version-property/version-edit/version-edit.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { VersionEditComponent } from './version-edit.component'; @@ -9,13 +10,19 @@ describe('VersionEditComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [VersionEditComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(VersionEditComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { + mode: 'edit', + object: {} as any, + field: 'x_mitre_version', + }; + component.config.object[component.field] = { version: '1.0' }; }); it('should create', () => { diff --git a/src/app/components/stix/version-property/version-property.component.spec.ts b/src/app/components/stix/version-property/version-property.component.spec.ts index 312586226..dbfeae72d 100644 --- a/src/app/components/stix/version-property/version-property.component.spec.ts +++ b/src/app/components/stix/version-property/version-property.component.spec.ts @@ -1,6 +1,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { VersionPropertyComponent } from './version-property.component'; +import { StixObject } from 'src/app/classes/stix/stix-object'; describe('VersionPropertyComponent', () => { let component: VersionPropertyComponent; @@ -9,13 +12,19 @@ describe('VersionPropertyComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [VersionPropertyComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [provideHttpClient()], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(VersionPropertyComponent); component = fixture.componentInstance; - fixture.detectChanges(); + // Set required config input + component.config = { + mode: 'view', + object: {} as StixObject, + }; }); it('should create', () => { diff --git a/src/app/components/stix/version-property/version-view/version-view.component.spec.ts b/src/app/components/stix/version-property/version-view/version-view.component.spec.ts index a8fa7d8f3..a04cb50b6 100644 --- a/src/app/components/stix/version-property/version-view/version-view.component.spec.ts +++ b/src/app/components/stix/version-property/version-view/version-view.component.spec.ts @@ -1,6 +1,9 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { VersionViewComponent } from './version-view.component'; +import { Technique } from 'src/app/classes/stix/technique'; +import { VersionNumber } from 'src/app/classes/version-number'; describe('VersionViewComponent', () => { let component: VersionViewComponent; @@ -9,12 +12,22 @@ describe('VersionViewComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [VersionViewComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(VersionViewComponent); component = fixture.componentInstance; + + // Initialize the required config input with a mock + const mockObject = new Technique(); + mockObject.version = new VersionNumber('1.0'); + component.config = { + object: mockObject, + mode: 'view', + }; + fixture.detectChanges(); }); diff --git a/src/app/components/subheading/subheading.component.spec.ts b/src/app/components/subheading/subheading.component.spec.ts index 6730d1006..c4532435e 100644 --- a/src/app/components/subheading/subheading.component.spec.ts +++ b/src/app/components/subheading/subheading.component.spec.ts @@ -1,4 +1,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; +import { provideHttpClient } from '@angular/common/http'; +import { of } from 'rxjs'; +import { MtxPopoverModule } from '@ng-matero/extensions/popover'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { SubheadingComponent } from './subheading.component'; @@ -9,12 +14,25 @@ describe('SubheadingComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [SubheadingComponent], + imports: [MtxPopoverModule], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + }, + }, + provideHttpClient(), + ], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(SubheadingComponent); component = fixture.componentInstance; + component.config = { object: {} }; fixture.detectChanges(); }); diff --git a/src/app/components/toolbar/toolbar.component.spec.ts b/src/app/components/toolbar/toolbar.component.spec.ts index f1ddc329a..9adf63ec1 100644 --- a/src/app/components/toolbar/toolbar.component.spec.ts +++ b/src/app/components/toolbar/toolbar.component.spec.ts @@ -1,4 +1,8 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; +import { provideHttpClient } from '@angular/common/http'; +import { of } from 'rxjs'; import { ToolbarComponent } from './toolbar.component'; @@ -9,6 +13,17 @@ describe('ObjectHeaderComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ToolbarComponent], + providers: [ + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + }, + }, + provideHttpClient(), + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); })); diff --git a/src/app/components/users-list/users-list.component.spec.ts b/src/app/components/users-list/users-list.component.spec.ts index c1a3566f7..ed7c3231c 100644 --- a/src/app/components/users-list/users-list.component.spec.ts +++ b/src/app/components/users-list/users-list.component.spec.ts @@ -1,20 +1,51 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; import { UsersListComponent } from './users-list.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { AuthenticationService } from 'src/app/services/connectors/authentication/authentication.service'; +import { + createMockRestApiConnector, + createAsyncObservable, + createPaginatedResponse, +} from 'src/app/testing/mocks/rest-api-connector.mock'; +import { createMockAuthenticationService } from 'src/app/testing/mocks/authentication-service.mock'; describe('UsersListComponent', () => { let component: UsersListComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getAllUserAccounts: () => + createAsyncObservable(createPaginatedResponse()), + getTeamsByUserId: () => createAsyncObservable([]), + }); + const mockAuthService = createMockAuthenticationService({}); + await TestBed.configureTestingModule({ declarations: [UsersListComponent], + providers: [ + provideHttpClient(), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + { provide: AuthenticationService, useValue: mockAuthService }, + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(UsersListComponent); component = fixture.componentInstance; + component.config = { + mode: 'view', + columnsToDisplay: [], + team: null, + showSearch: false, + showFilters: false, + selection: null, + } as any; fixture.detectChanges(); }); diff --git a/src/app/components/validation-results/validation-results.component.spec.ts b/src/app/components/validation-results/validation-results.component.spec.ts index 69d1ecb5e..c7727eb12 100644 --- a/src/app/components/validation-results/validation-results.component.spec.ts +++ b/src/app/components/validation-results/validation-results.component.spec.ts @@ -1,4 +1,6 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ValidationData } from 'src/app/classes/serializable'; import { ValidationResultsComponent } from './validation-results.component'; @@ -9,12 +11,17 @@ describe('ValidationResultsComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ValidationResultsComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(ValidationResultsComponent); component = fixture.componentInstance; + + // Initialize required validation input + component.validation = new ValidationData(); + fixture.detectChanges(); }); diff --git a/src/app/components/version-popover/version-popover.component.spec.ts b/src/app/components/version-popover/version-popover.component.spec.ts index 9b111a550..e68f97e0c 100644 --- a/src/app/components/version-popover/version-popover.component.spec.ts +++ b/src/app/components/version-popover/version-popover.component.spec.ts @@ -1,6 +1,9 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MtxPopoverModule } from '@ng-matero/extensions/popover'; import { VersionPopoverComponent } from './version-popover.component'; +import { StixObject } from 'src/app/classes/stix/stix-object'; describe('VersionPopoverComponent', () => { let component: VersionPopoverComponent; @@ -9,12 +12,18 @@ describe('VersionPopoverComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [VersionPopoverComponent], + imports: [MtxPopoverModule], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(VersionPopoverComponent); component = fixture.componentInstance; + // Set required config input + component.config = { + object: {} as StixObject, + }; fixture.detectChanges(); }); diff --git a/src/app/services/connectors/authentication/authentication.service.spec.ts b/src/app/services/connectors/authentication/authentication.service.spec.ts index 5c9886764..d19246ffd 100644 --- a/src/app/services/connectors/authentication/authentication.service.spec.ts +++ b/src/app/services/connectors/authentication/authentication.service.spec.ts @@ -1,4 +1,6 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; import { AuthenticationService } from './authentication.service'; @@ -6,7 +8,10 @@ describe('AuthenticationService', () => { let service: AuthenticationService; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + providers: [provideHttpClient()], + schemas: [NO_ERRORS_SCHEMA], + }); service = TestBed.inject(AuthenticationService); }); diff --git a/src/app/services/connectors/rest-api/rest-api-connector.service.spec.ts b/src/app/services/connectors/rest-api/rest-api-connector.service.spec.ts index 0218e463b..2ce69e90f 100644 --- a/src/app/services/connectors/rest-api/rest-api-connector.service.spec.ts +++ b/src/app/services/connectors/rest-api/rest-api-connector.service.spec.ts @@ -1,4 +1,6 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; import { RestApiConnectorService } from './rest-api-connector.service'; @@ -6,7 +8,10 @@ describe('RestApiConnectorService', () => { let service: RestApiConnectorService; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + providers: [provideHttpClient()], + schemas: [NO_ERRORS_SCHEMA], + }); service = TestBed.inject(RestApiConnectorService); }); diff --git a/src/app/services/connectors/rest-api/rest-api-connector.service.ts b/src/app/services/connectors/rest-api/rest-api-connector.service.ts index 58d226a86..59e252e56 100644 --- a/src/app/services/connectors/rest-api/rest-api-connector.service.ts +++ b/src/app/services/connectors/rest-api/rest-api-connector.service.ts @@ -1119,6 +1119,42 @@ export class RestApiConnectorService extends ApiConnector { }; } + /** + * Factory to create a STIX object validator via dryRun POST. + * + * Posts the serialized object to its type-specific endpoint with ?dryRun=true, + * which runs the full create pipeline (compose, validate) without persisting. + * Normalizes the response into { errors, warnings } for the caller. + * + * @template T the type to validate + * @returns validator function + */ + public validateStixObject() { + return

(object: P): Observable => { + const plural = AttackTypeToPlural[object.attackType]; + const url = `${this.apiUrl}/${plural}`; + const params = new HttpParams().set('dryRun', 'true'); + return this.http.post(url, object.serialize(), { params }).pipe( + // Success (200): validation passed, extract warnings only + map(result => ({ + errors: [], + warnings: (result as any).warnings || [], + })), + catchError((error: any) => { + // Validation failure (400): normalize errors and warnings into expected shape + if (error.status === 400 && error.error?.details) { + return of({ + errors: error.error.details, + warnings: error.error.warnings || [], + }); + } + // Non-validation errors: re-raise + return throwError(error); + }), + share() + ); + }; + } /** * POST (create) a new technique * @param {Technique} object the object to create @@ -1980,6 +2016,101 @@ export class RestApiConnectorService extends ApiConnector { ); } + /** + * Stream collection bundle import with progress updates using Server-Sent Events + * @param collectionBundle the STIX bundle to import + * @param force whether to force import despite warnings + * @returns Observable that emits progress events and final collection + */ + public streamCollectionBundleImport( + collectionBundle: any, + force = false + ): Observable<{ type: string; data: any }> { + return new Observable(observer => { + let query = new HttpParams(); + query = query.set('stream', 'true'); + if (force) query = query.set('forceImport', 'all'); + + const url = `${this.apiUrl}/collection-bundles?${query.toString()}`; + + // Note: EventSource doesn't support POST with body, so we need to use fetch + // with SSE streaming instead + fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'text/event-stream', + }, + credentials: 'include', + body: JSON.stringify(collectionBundle), + }) + .then(async response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const reader = response.body?.getReader(); + const decoder = new TextDecoder(); + + if (!reader) { + throw new Error('No response body'); + } + + let buffer = ''; + + while (true) { + const { done, value } = await reader.read(); + + if (done) { + observer.complete(); + break; + } + + buffer += decoder.decode(value, { stream: true }); + const lines = buffer.split('\n\n'); + buffer = lines.pop() || ''; + + for (const line of lines) { + if (line.trim() === '' || line.startsWith(':')) continue; + + const eventMatch = line.match(/^event: (\w+)\n/); + const dataMatch = line.match(/data: (.+)$/m); + + if (dataMatch) { + try { + const data = JSON.parse(dataMatch[1]); + const eventType = eventMatch ? eventMatch[1] : 'message'; + + if (eventType === 'error') { + observer.error(data); + return; + } + + observer.next({ type: eventType, data }); + + if (eventType === 'complete') { + observer.complete(); + return; + } + } catch (e) { + logger.error('Error parsing SSE data:', e); + } + } + } + } + }) + .catch(err => { + logger.error('Stream error:', err); + observer.error(err); + }); + + // Cleanup + return () => { + // EventSource cleanup if needed + }; + }); + } + /** * Preview a collection bundle. * POST the collection bundle to the back end to retrieve a preview of the import results. A second POST @@ -2330,6 +2461,33 @@ export class RestApiConnectorService extends ApiConnector { ); } + /** + * Get the next available ATT&CK ID for a given STIX type + * @param {string} stixType the STIX type (e.g., 'attack-pattern', 'x-mitre-tactic') + * @param {string} [parentRef] optional parent technique STIX ID for subtechniques + * @returns {Observable} the next available ATT&CK ID + */ + public getNextAttackId( + stixType: string, + parentRef?: string + ): Observable { + let params = new HttpParams().set('type', stixType); + if (parentRef) { + params = params.set('parentRef', parentRef); + } + + return this.http + .get<{ + attack_id: string; + }>(`${this.apiUrl}/attack-objects/attack-id/next`, { params }) + .pipe( + tap(_ => logger.log(`retrieved next ATT&CK ID for ${stixType}`)), + map(result => result.attack_id), + catchError(this.handleError_continue()), + share() + ); + } + // _ _ ___ ___ ___ _ ___ ___ ___ _ _ _ _ _____ _ ___ ___ ___ // | | | / __| __| _ \ /_\ / __/ __/ _ \| | | | \| |_ _| /_\ | _ \_ _/ __| // | |_| \__ \ _|| / / _ \ (_| (_| (_) | |_| | .` | | | / _ \| _/| |\__ \ @@ -2662,6 +2820,39 @@ export class RestApiConnectorService extends ApiConnector { }); return getter; } + + // ___ _ + // | _ \___ _ __ ___ _ _ __| |_ ___ + // | / -_) '_ \/ _ \ '_/ _| _(_-< + // |_|_\___| .__/\___/_| \__|\__/__/ + // |_| + + /** + * Retrieve objects which have unresolved link-by-id references + */ + public getMissingLinkById(): Observable { + const url = `${this.apiUrl}/reports/link-by-id/missing`; + return this.http.get(url).pipe( + tap(results => + logger.log('retrieved missing link-by-id report', results) + ), + catchError(this.handleError_continue([])), // on error, trigger the error notification and continue operation without crashing (returns empty item) + share() // multicast so that multiple subscribers don't trigger the call twice. THIS MUST BE THE LAST LINE OF THE PIPE + ); + } + /** + * Retrieve groups of parallel relationships between the same source/target/type + */ + public getParallelRelationships(): Observable { + const url = `${this.apiUrl}/reports/parallel-relationships`; + return this.http.get(url).pipe( + tap(results => + logger.log('retrieved parallel relationships report', results) + ), + catchError(this.handleError_continue([])), // on error, trigger the error notification and continue operation without crashing (returns empty item) + share() // multicast so that multiple subscribers don't trigger the call twice. THIS MUST BE THE LAST LINE OF THE PIPE + ); + } } class CustomEncoder implements HttpParameterCodec { diff --git a/src/app/services/editor/editor.service.spec.ts b/src/app/services/editor/editor.service.spec.ts index cbead93a6..02bcb393f 100644 --- a/src/app/services/editor/editor.service.spec.ts +++ b/src/app/services/editor/editor.service.spec.ts @@ -1,4 +1,8 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TestBed } from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; +import { provideHttpClient } from '@angular/common/http'; +import { of } from 'rxjs'; import { EditorService } from './editor.service'; @@ -6,7 +10,19 @@ describe('EditorService', () => { let service: EditorService; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + providers: [ + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + }, + }, + provideHttpClient(), + ], + schemas: [NO_ERRORS_SCHEMA], + }); service = TestBed.inject(EditorService); }); diff --git a/src/app/services/editor/editor.service.ts b/src/app/services/editor/editor.service.ts index e7e5c8290..16f8c36a3 100644 --- a/src/app/services/editor/editor.service.ts +++ b/src/app/services/editor/editor.service.ts @@ -128,7 +128,7 @@ export class EditorService { if (!this.router.url.includes('/new')) this.router.navigate([], { queryParams: {} }); else this.router.navigate(['..'], { queryParams: {} }); - this.onEditingStopped.emit(); + this.onEditingStopped.emit(true); } }, complete: () => { diff --git a/src/app/services/helpers/auth.interceptor.spec.ts b/src/app/services/helpers/auth.interceptor.spec.ts index 8e5ddfed3..1967c840b 100644 --- a/src/app/services/helpers/auth.interceptor.spec.ts +++ b/src/app/services/helpers/auth.interceptor.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { AuthInterceptor } from './auth.interceptor'; @@ -6,6 +7,7 @@ describe('AuthInterceptor', () => { beforeEach(() => TestBed.configureTestingModule({ providers: [AuthInterceptor], + schemas: [NO_ERRORS_SCHEMA], }) ); diff --git a/src/app/services/helpers/authorization.guard.spec.ts b/src/app/services/helpers/authorization.guard.spec.ts index 74f6f6976..dfeaa95ce 100644 --- a/src/app/services/helpers/authorization.guard.spec.ts +++ b/src/app/services/helpers/authorization.guard.spec.ts @@ -1,4 +1,6 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; import { AuthorizationGuard } from './authorization.guard'; @@ -6,7 +8,10 @@ describe('AuthorizationGuard', () => { let guard: AuthorizationGuard; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + providers: [provideHttpClient()], + schemas: [NO_ERRORS_SCHEMA], + }); guard = TestBed.inject(AuthorizationGuard); }); diff --git a/src/app/services/helpers/breadcrumb.service.spec.ts b/src/app/services/helpers/breadcrumb.service.spec.ts index 472f6db0e..fb6e6e2d3 100644 --- a/src/app/services/helpers/breadcrumb.service.spec.ts +++ b/src/app/services/helpers/breadcrumb.service.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { BreadcrumbService } from './breadcrumb.service'; @@ -6,7 +7,9 @@ describe('BreadcrumbService', () => { let service: BreadcrumbService; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + schemas: [NO_ERRORS_SCHEMA], + }); service = TestBed.inject(BreadcrumbService); }); diff --git a/src/app/services/sidebar/sidebar.service.spec.ts b/src/app/services/sidebar/sidebar.service.spec.ts index aba44f7e7..7b2bac58f 100644 --- a/src/app/services/sidebar/sidebar.service.spec.ts +++ b/src/app/services/sidebar/sidebar.service.spec.ts @@ -1,12 +1,15 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { SidebarService } from './sidebar.service'; -describe('SidebarManagerService', () => { - let service: SidebarManagerService; +describe('SidebarService', () => { + let service: SidebarService; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + schemas: [NO_ERRORS_SCHEMA], + }); service = TestBed.inject(SidebarService); }); diff --git a/src/app/services/title/title.service.spec.ts b/src/app/services/title/title.service.spec.ts index 39294f082..dd293dc4b 100644 --- a/src/app/services/title/title.service.spec.ts +++ b/src/app/services/title/title.service.spec.ts @@ -1,4 +1,7 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TestBed } from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; +import { of } from 'rxjs'; import { TitleService } from './title.service'; @@ -6,7 +9,19 @@ describe('TitleService', () => { let service: TitleService; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + providers: [ + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + snapshot: { params: {}, queryParams: {} }, + }, + }, + ], + schemas: [NO_ERRORS_SCHEMA], + }); service = TestBed.inject(TitleService); }); diff --git a/src/app/services/website-integration/website-integration.service.spec.ts b/src/app/services/website-integration/website-integration.service.spec.ts index a9ea589ac..c1a69988f 100644 --- a/src/app/services/website-integration/website-integration.service.spec.ts +++ b/src/app/services/website-integration/website-integration.service.spec.ts @@ -1,4 +1,8 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TestBed } from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; +import { provideHttpClient } from '@angular/common/http'; +import { of } from 'rxjs'; import { WebsiteIntegrationService } from './website-integration.service'; @@ -6,7 +10,19 @@ describe('WebsiteIntegrationService', () => { let service: WebsiteIntegrationService; beforeEach(() => { - TestBed.configureTestingModule({}); + TestBed.configureTestingModule({ + providers: [ + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + }, + }, + provideHttpClient(), + ], + schemas: [NO_ERRORS_SCHEMA], + }); service = TestBed.inject(WebsiteIntegrationService); }); diff --git a/src/app/testing/mocks/authentication-service.mock.ts b/src/app/testing/mocks/authentication-service.mock.ts new file mode 100644 index 000000000..9546536ef --- /dev/null +++ b/src/app/testing/mocks/authentication-service.mock.ts @@ -0,0 +1,145 @@ +import { EventEmitter } from '@angular/core'; +import { Observable } from 'rxjs'; +import { Role } from 'src/app/classes/authn/role'; +import { Status } from 'src/app/classes/authn/status'; +import { UserAccount } from 'src/app/classes/authn/user-account'; +import { createAsyncObservable } from './rest-api-connector.mock'; + +/** + * Creates a mock UserAccount for testing purposes. + * + * @param overrides - Partial UserAccount properties to override defaults + * @returns A UserAccount object with sensible test defaults + * + * @example + * const user = createMockUserAccount({ role: Role.ADMIN }); + */ +export function createMockUserAccount( + overrides?: Partial +): UserAccount { + const mockUser = new UserAccount({ + id: 'mock-user-id', + username: 'testuser', + email: 'test@example.com', + displayName: 'Test User', + status: Status.ACTIVE, + role: Role.VISITOR, + created: new Date().toISOString(), + modified: new Date().toISOString(), + }); + + // Apply overrides if provided + if (overrides) { + Object.assign(mockUser, overrides); + } + + return mockUser; +} + +/** + * Creates a mock AuthenticationService with configurable method implementations. + * + * @param config - Partial object with method implementations to override + * @returns A mock object that can be used in Angular tests + * + * @example + * // Basic mock with logged-out user + * const mock = createMockAuthenticationService({}); + * + * @example + * // Mock with logged-in admin user + * const mock = createMockAuthenticationService({ + * currentUser: createMockUserAccount({ role: Role.ADMIN }), + * isLoggedIn: true + * }); + * + * @example + * // Mock with custom authorization logic + * const mock = createMockAuthenticationService({ + * isAuthorized: (roles: Role[]) => roles.includes(Role.EDITOR) + * }); + */ +export function createMockAuthenticationService(config?: any): any { + // Default mock user (not logged in) + const defaultUser = new UserAccount({ + id: '', + username: '', + email: '', + displayName: '', + status: Status.PENDING, + role: Role.NONE, + created: new Date().toISOString(), + modified: new Date().toISOString(), + }); + + const defaults = { + // Properties + currentUser: defaultUser, + activeRoles: [Role.ADMIN, Role.EDITOR, Role.TEAM_LEAD, Role.VISITOR], + inactiveRoles: [Role.NONE], + onLogin: new EventEmitter(), + + // Computed property + get isLoggedIn(): boolean { + return this.currentUser && this.currentUser.status === Status.ACTIVE; + }, + + // Methods + isAuthorized: (roles: Role[]): boolean => { + const mock = config?.currentUser || defaultUser; + if (!mock || mock.status !== Status.ACTIVE) return false; + return roles.indexOf(mock.role) > -1; + }, + + canEdit: (attackType?: string): boolean => { + const mock = config?.currentUser || defaultUser; + if (!mock || mock.status !== Status.ACTIVE) return false; + + if ( + attackType && + (attackType.includes('collection') || + attackType.includes('marking-definition')) + ) { + return mock.role === Role.ADMIN; + } + return [Role.EDITOR, Role.TEAM_LEAD, Role.ADMIN].indexOf(mock.role) > -1; + }, + + canDelete: (): boolean => { + const mock = config?.currentUser || defaultUser; + if (!mock || mock.status !== Status.ACTIVE) return false; + return mock.role === Role.ADMIN; + }, + + login: (): Observable => { + return createAsyncObservable(config?.currentUser || defaultUser); + }, + + logout: (): Observable => { + return createAsyncObservable({}); + }, + + register: (): Observable => { + return createAsyncObservable({}); + }, + + handleRegisterRedirect: (): Observable => { + return createAsyncObservable({}); + }, + + getSession: (): Observable => { + return createAsyncObservable(config?.currentUser || defaultUser); + }, + + getAuthType: (): Observable => { + return createAsyncObservable('anonymous'); + }, + + success: (): void => { + // Mock success method + }, + }; + + // Merge config with defaults + return { ...defaults, ...config }; +} diff --git a/src/app/testing/mocks/rest-api-connector.mock.ts b/src/app/testing/mocks/rest-api-connector.mock.ts new file mode 100644 index 000000000..1b5a4e7f7 --- /dev/null +++ b/src/app/testing/mocks/rest-api-connector.mock.ts @@ -0,0 +1,62 @@ +import { Observable } from 'rxjs'; +import { Paginated } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; + +/** + * Creates an async Observable that emits the provided data after a setTimeout(0). + * This prevents subscription initialization bugs in Angular tests. + * + * @param data - The data to emit from the Observable + * @returns An Observable that emits the data asynchronously + */ +export function createAsyncObservable(data: T): Observable { + return new Observable(subscriber => { + setTimeout(() => { + subscriber.next(data); + subscriber.complete(); + }, 0); + }); +} + +/** + * Creates a standard paginated response structure with empty data by default. + * + * @param data - Optional array of data items (defaults to empty array) + * @returns A Paginated response object with the provided data + */ +export function createPaginatedResponse(data: T[] = []): Paginated { + return { + data, + pagination: { + total: 0, + limit: -1, + offset: -1, + }, + }; +} + +/** + * Creates a mock RestApiConnectorService with configurable method implementations. + * + * @param config - Partial object with method implementations to override + * @returns A mock object that can be used in Angular tests + * + * @example + * // Simple paginated method mock + * const mock = createMockRestApiConnector({ + * getAllNotes: () => createAsyncObservable(createPaginatedResponse()) + * }); + * + * @example + * // Custom response mock + * const mock = createMockRestApiConnector({ + * getOrganizationIdentity: () => createAsyncObservable({ name: 'Test Org' }) + * }); + */ +export function createMockRestApiConnector(config?: any): any { + const defaults = { + // Default implementation for getAllAllowedValues to prevent HTTP errors in tests + getAllAllowedValues: () => createAsyncObservable([]), + }; + + return { ...defaults, ...config }; +} diff --git a/src/app/utils/types.ts b/src/app/utils/types.ts index 66d8258fb..50e02d2db 100644 --- a/src/app/utils/types.ts +++ b/src/app/utils/types.ts @@ -46,17 +46,12 @@ export type StixType = /** * Workflow state definitions */ -export type WorkflowState = - | '' - | 'work-in-progress' - | 'awaiting-review' - | 'reviewed'; +export type WorkflowState = 'work-in-progress' | 'awaiting-review' | 'reviewed'; /** * List of all workflow states */ export const WorkflowStates: Record = { - '': 'none', 'work-in-progress': 'work in progress', 'awaiting-review': 'awaiting review', 'reviewed': 'reviewed', diff --git a/src/app/views/collection-manager/collection-manager.component.spec.ts b/src/app/views/collection-manager/collection-manager.component.spec.ts index 24324f60f..fdcdc5418 100644 --- a/src/app/views/collection-manager/collection-manager.component.spec.ts +++ b/src/app/views/collection-manager/collection-manager.component.spec.ts @@ -1,14 +1,32 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; import { CollectionManagerComponent } from './collection-manager.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createAsyncObservable, + createMockRestApiConnector, + createPaginatedResponse, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('CollectionManagerComponent', () => { let component: CollectionManagerComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getAllCollections: () => + createAsyncObservable(createPaginatedResponse([])), + }); + await TestBed.configureTestingModule({ declarations: [CollectionManagerComponent], + providers: [ + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + provideHttpClient(), + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); diff --git a/src/app/views/contributors-page/contributors-page.component.spec.ts b/src/app/views/contributors-page/contributors-page.component.spec.ts index 304094d84..8a443d94c 100644 --- a/src/app/views/contributors-page/contributors-page.component.spec.ts +++ b/src/app/views/contributors-page/contributors-page.component.spec.ts @@ -1,14 +1,33 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { MtxPopoverModule } from '@ng-matero/extensions/popover'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ContributorsPageComponent } from './contributors-page.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createMockRestApiConnector, + createAsyncObservable, + createPaginatedResponse, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('ContributorsPageComponent', () => { let component: ContributorsPageComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getAllObjects: () => createAsyncObservable(createPaginatedResponse()), + }); + await TestBed.configureTestingModule({ declarations: [ContributorsPageComponent], + imports: [MtxPopoverModule], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + provideHttpClient(), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], }).compileComponents(); fixture = TestBed.createComponent(ContributorsPageComponent); diff --git a/src/app/views/dashboard-page/dashboard-page.component.html b/src/app/views/dashboard-page/dashboard-page.component.html index 5c256ca6c..aa9236846 100644 --- a/src/app/views/dashboard-page/dashboard-page.component.html +++ b/src/app/views/dashboard-page/dashboard-page.component.html @@ -1,10 +1,33 @@

-
+

Dashboard

-
+
+
+
+ + +
+
+
+
+
+

Admin Settings

+
+
+
-
-
-
-
-
-
- - + + Selecting one relationship will mark the remaining + {{ group.toDeprecate.length }} for deprecation + +
+ + +
+ No parallel relationships found. +
+ + +

Missing LinkByIds Status

+ +
{{ error }}
+ + + + + Missing Links by ID ({{ missingLinks.length }}) + + + + + +
+ No missing link by ids found. +
+
diff --git a/src/app/views/dashboard-page/data-quality/data-quality.component.scss b/src/app/views/dashboard-page/data-quality/data-quality.component.scss new file mode 100644 index 000000000..8915dce81 --- /dev/null +++ b/src/app/views/dashboard-page/data-quality/data-quality.component.scss @@ -0,0 +1,52 @@ +@use '../../../../style/globals'; + +.data-quality-subheader { + @extend .text-label; + @extend .text-deemphasis; + text-align: center; + text-transform: uppercase; + margin-bottom: 20px; +} + +.dq-accordion { + .mat-expansion-panel-header-title { + font-family: Roboto, Arial, sans-serif; + font-weight: 500; + font-size: 1.2rem; /* larger header text */ + line-height: 1.3; + } + .dq-rel-table { + width: 100%; + } + .dq-actions { + display: flex; + align-items: center; + gap: 12px; + margin: 8px 0 16px; + } + .dq-note { + font-size: 0.9rem; + opacity: 0.8; + } +} + +.missing-list { + .missing-id { + font-family: Roboto, Arial, sans-serif; + font-size: 1rem; + word-break: break-all; + } + .missing-item { + padding: 8px 0; + border-bottom: 1px solid #eee; + } + .missing-rel-header { + font-weight: 600; + font-size: 1.05rem; + } + .missing-rel-description, + .missing-description { + margin-top: 4px; + font-size: 0.95rem; + } +} diff --git a/src/app/views/dashboard-page/data-quality/data-quality.component.spec.ts b/src/app/views/dashboard-page/data-quality/data-quality.component.spec.ts new file mode 100644 index 000000000..3bf741845 --- /dev/null +++ b/src/app/views/dashboard-page/data-quality/data-quality.component.spec.ts @@ -0,0 +1,31 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { of } from 'rxjs'; +import { DataQualityComponent } from './data-quality.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; + +describe('DataQualityComponent', () => { + let component: DataQualityComponent; + let fixture: ComponentFixture; + + const mockReportService = { + getMissingLinkById: () => of([]), + getParallelRelationships: () => of({}), + }; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [DataQualityComponent], + providers: [ + { provide: RestApiConnectorService, useValue: mockReportService }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(DataQualityComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/views/dashboard-page/data-quality/data-quality.component.ts b/src/app/views/dashboard-page/data-quality/data-quality.component.ts new file mode 100644 index 000000000..4d983adda --- /dev/null +++ b/src/app/views/dashboard-page/data-quality/data-quality.component.ts @@ -0,0 +1,285 @@ +import { Component, OnInit, ViewEncapsulation, ViewChild } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { StixObject } from 'src/app/classes/stix/stix-object'; +import { StixListConfig } from 'src/app/components/stix/stix-list/stix-list.component'; +import { StixTypeToAttackType } from 'src/app/utils/type-mappings'; +import { StixListComponent } from 'src/app/components/stix/stix-list/stix-list.component'; +import { SelectionModel } from '@angular/cdk/collections'; +import { forkJoin } from 'rxjs'; +import { Relationship } from 'src/app/classes/stix/relationship'; +import { ConfirmationDialogComponent } from 'src/app/components/confirmation-dialog/confirmation-dialog.component'; + +interface ParallelRelationshipGroup { + key: string; + sourceRef: string; + targetRef: string; + relationshipType: string; + count: number; + relationships: any[]; + stixObjects?: StixObject[]; + selectedRelationship: string; + toDeprecate: string[]; + selection?: SelectionModel; +} + +@Component({ + selector: 'app-data-quality', + templateUrl: './data-quality.component.html', + styleUrls: ['./data-quality.component.scss'], + encapsulation: ViewEncapsulation.None, + standalone: false, +}) +export class DataQualityComponent implements OnInit { + constructor( + private reportService: RestApiConnectorService, + private dialog: MatDialog + ) {} + + missingLinks: any[] = []; + missingLinkRows: { id: string; name: string }[] = []; + loadingMissingLinks = false; + error?: string; + + parallelRelationships: ParallelRelationshipGroup[] = []; + loadingParallel = false; + parallelError?: string; + + stixRelationshipConfig: StixListConfig = { + type: 'relationship', + stixObjects: [], + clickBehavior: 'none', + showFilters: false, + showDeprecatedFilter: true, + compactRelationshipColumns: true, + }; + + @ViewChild('relationshipList') relationshipList: StixListComponent; + + ngOnInit(): void { + this.loadParallelRelationships(); + this.loadMissingLinks(); + } + + private transformParallelRelationships( + raw: Record + ): ParallelRelationshipGroup[] { + return Object.entries(raw).map(([key, relationships]) => { + const byNewest = [...relationships].sort((a, b) => { + const ac = a.stix?.created; + const bc = b.stix?.created; + return bc - ac; + }); + const selectedRelationship = + byNewest[0].stix.id ?? relationships[0].stix.id; + const toDeprecate = relationships + .map(r => r.stix.id) + .filter(id => id !== selectedRelationship); + const selection = new SelectionModel(false); + if (selectedRelationship) selection.toggle(selectedRelationship); + + const first = relationships[0].stix; + return { + key, + sourceRef: first?.source_ref, + targetRef: first?.target_ref, + relationshipType: first?.relationship_type, + count: relationships.length, + relationships, + selectedRelationship, + toDeprecate, + selection, + }; + }); + } + + // Build a link to the workbench object page using STIX->ATT&CK mapping + objectLink(item: any): any[] { + const id = item.stix.id; + const stixType = item.stix.type as keyof typeof StixTypeToAttackType; + const attackType = StixTypeToAttackType[stixType]; + return ['/', attackType, id]; + } + + loadMissingLinks(): void { + this.loadingMissingLinks = true; + this.reportService.getMissingLinkById().subscribe({ + next: data => { + // update later to also display relationship missing link by ids + this.missingLinks = Array.isArray(data) + ? data.filter(item => { + const type = item.stix.type; + return type !== 'relationship'; + }) + : []; + this.missingLinkRows = this.buildMissingLinkRows(); + this.loadingMissingLinks = false; + }, + error: err => { + this.error = 'Failed to load report'; + this.loadingMissingLinks = false; + console.error(err); + }, + }); + } + + loadParallelRelationships(): void { + this.loadingParallel = true; + + this.reportService.getParallelRelationships().subscribe({ + next: rawData => { + this.parallelRelationships = + this.transformParallelRelationships(rawData); + + this.parallelRelationships.forEach(group => { + group.stixObjects = this.buildStixObjectsForGroup(group); + }); + this.loadingParallel = false; + this.parallelError = undefined; + }, + error: err => { + this.parallelError = 'Failed to load parallel relationships report'; + this.loadingParallel = false; + console.error(err); + }, + }); + } + + // Build a StixList-compatible array for a given group + buildStixObjectsForGroup(group: ParallelRelationshipGroup): StixObject[] { + if (!group || !Array.isArray(group.relationships)) return [] as any; + return group.relationships.map(r => this.mapRelationshipToRow(r)) as any; + } + + // Use existing config and override only the stixObjects and hide controls in panels + stixConfigForGroup(group: ParallelRelationshipGroup): StixListConfig { + return { + ...this.stixRelationshipConfig, + stixObjects: group.stixObjects || this.buildStixObjectsForGroup(group), + showControls: false, + select: 'one', + selectionModel: group.selection, + }; + } + + // Build stix objects for missing links + buildMissingLinkObjects(): StixObject[] { + return (this.missingLinks || []).map(item => { + const s = item?.stix || item; + return { + stixID: s.id, + attackType: StixTypeToAttackType[s.type], + type: s.type, + attackID: s.external_references?.[0]?.external_id || '', + name: s.name || s.external_references?.[0]?.external_id || s.id, + } as any; + }); + } + + // Use stix-list for missing links with a 2 columns - id and name + stixConfigForMissingLinks(): StixListConfig { + return { + type: 'relationship', // use relationship so stix-list table styling matches + stixObjects: this.buildMissingLinkObjects(), + columnsPreset: 'id-name', + showControls: false, + showFilters: false, + showDeprecatedFilter: false, + clickBehavior: 'linkToObjectPage', + }; + } + + private mapRelationshipToRow(r: any): StixObject { + const stix = r.stix; + return { + stixID: stix.id, + type: 'relationship', + source_ref: stix.source_ref || '', + target_ref: stix.target_ref || '', + source_name: r.source_object.stix.name || '', + target_name: r.target_object.stix.name || '', + source_ID: r.source_object.stix.external_references[0].external_id || '', + target_ID: r.target_object.stix.external_references[0].external_id || '', + relationship_type: stix.relationship_type, + description: stix.description || '', + created: stix.created || undefined, + modified: stix.modified || undefined, + revoked: r?.revoked, + deprecated: r?.x_mitre_deprecated, + } as any; + } + // Build rows for missing links table and display id and name + buildMissingLinkRows(): { id: string; name: string }[] { + return (this.missingLinks || []).map(item => { + const id = + item?.stix.external_references?.[0]?.external_id || item?.stix.id || ''; + const name = item?.stix.name || ''; + return { id, name }; + }); + } + + // Handle selection change from stix-list radios + onGroupSelect(element: StixObject, group: ParallelRelationshipGroup): void { + if (!group || !group.selection) return; + const stixId = element.stixID; + if (!stixId) return; + group.selection.clear(); + group.selection.select(stixId); + group.selectedRelationship = stixId; + group.toDeprecate = group.relationships + .map(r => r.stix.id) + .filter(id => id && id !== group.selectedRelationship); + group.stixObjects = this.buildStixObjectsForGroup(group); + } + + // Deprecate non-selected relationships for a group + deprecateOthers(group: ParallelRelationshipGroup): void { + if (!group || !group.toDeprecate?.length) return; + + const confirmationPrompt = this.dialog.open(ConfirmationDialogComponent, { + maxWidth: '35em', + data: { + message: + 'All selected relationships in this group will be deprecated. Do you want to continue?', + }, + autoFocus: false, // prevents auto focus on toolbar buttons + }); + + const confirmationSub = confirmationPrompt.afterClosed().subscribe({ + next: result => { + if (!result) return; // user cancelled + + const tasks = group.relationships + .filter( + r => + r.stix.id !== group.selectedRelationship && + !r?.x_mitre_deprecated && + !['subtechnique-of', 'revoked-by'].includes( + r.stix?.relationship_type + ) + ) + .map(r => { + const rel = new Relationship(r); + rel.deprecated = true; + return this.reportService.putRelationship(rel); + }); + + const sub = forkJoin(tasks).subscribe({ + next: () => { + group.relationships = group.relationships.filter( + r => r.stix?.id === group.selectedRelationship + ); + group.toDeprecate = []; + group.stixObjects = this.buildStixObjectsForGroup(group); + window.location.reload(); + }, + error: err => { + console.error(err); + }, + complete: () => sub.unsubscribe(), + }); + }, + complete: () => confirmationSub.unsubscribe(), + }); + } +} diff --git a/src/app/views/dashboard-page/default-marking-definitions/default-marking-definitions.component.spec.ts b/src/app/views/dashboard-page/default-marking-definitions/default-marking-definitions.component.spec.ts index c1012537c..c700b0ff9 100644 --- a/src/app/views/dashboard-page/default-marking-definitions/default-marking-definitions.component.spec.ts +++ b/src/app/views/dashboard-page/default-marking-definitions/default-marking-definitions.component.spec.ts @@ -1,21 +1,51 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { provideRouter } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { of } from 'rxjs'; +import { MtxPopoverModule } from '@ng-matero/extensions/popover'; import { DefaultMarkingDefinitionsComponent } from './default-marking-definitions.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createAsyncObservable, + createMockRestApiConnector, + createPaginatedResponse, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('DefaultMarkingDefinitionsComponent', () => { let component: DefaultMarkingDefinitionsComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getAllMarkingDefinitions: () => + createAsyncObservable(createPaginatedResponse([])), + }); + await TestBed.configureTestingModule({ declarations: [DefaultMarkingDefinitionsComponent], + imports: [MtxPopoverModule], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + provideHttpClient(), + provideRouter([]), + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + }, + }, + ], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(DefaultMarkingDefinitionsComponent); component = fixture.componentInstance; - fixture.detectChanges(); }); it('should create', () => { diff --git a/src/app/views/dashboard-page/org-settings-page/org-settings-page.component.spec.ts b/src/app/views/dashboard-page/org-settings-page/org-settings-page.component.spec.ts index 4f511fbb2..7d98cbbed 100644 --- a/src/app/views/dashboard-page/org-settings-page/org-settings-page.component.spec.ts +++ b/src/app/views/dashboard-page/org-settings-page/org-settings-page.component.spec.ts @@ -1,14 +1,32 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; import { OrgSettingsPageComponent } from './org-settings-page.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createMockRestApiConnector, + createAsyncObservable, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('OrgSettingsPageComponent', () => { let component: OrgSettingsPageComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getOrganizationIdentity: () => createAsyncObservable({}), + getOrganizationNamespace: () => + createAsyncObservable({ prefix: '', range_start: undefined }), + }); + await TestBed.configureTestingModule({ declarations: [OrgSettingsPageComponent], + providers: [ + provideHttpClient(), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); diff --git a/src/app/views/dashboard-page/teams/teams-list-page/teams-list-page.component.spec.ts b/src/app/views/dashboard-page/teams/teams-list-page/teams-list-page.component.spec.ts index ff9001e45..55132134d 100644 --- a/src/app/views/dashboard-page/teams/teams-list-page/teams-list-page.component.spec.ts +++ b/src/app/views/dashboard-page/teams/teams-list-page/teams-list-page.component.spec.ts @@ -1,21 +1,48 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { provideRouter } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { of } from 'rxjs'; import { TeamsListPageComponent } from './teams-list-page.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createMockRestApiConnector, + createAsyncObservable, + createPaginatedResponse, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('TeamsListPageComponent', () => { let component: TeamsListPageComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getAllTeams: () => createAsyncObservable(createPaginatedResponse()), + }); + await TestBed.configureTestingModule({ declarations: [TeamsListPageComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + provideHttpClient(), + provideRouter([]), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + }, + }, + ], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(TeamsListPageComponent); component = fixture.componentInstance; - fixture.detectChanges(); }); it('should create', () => { diff --git a/src/app/views/dashboard-page/teams/teams-view-page/teams-view-page.component.spec.ts b/src/app/views/dashboard-page/teams/teams-view-page/teams-view-page.component.spec.ts index eaa0fcac6..8aa77bda8 100644 --- a/src/app/views/dashboard-page/teams/teams-view-page/teams-view-page.component.spec.ts +++ b/src/app/views/dashboard-page/teams/teams-view-page/teams-view-page.component.spec.ts @@ -1,20 +1,51 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { provideRouter } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { of } from 'rxjs'; + import { TeamsViewPageComponent } from './teams-view-page.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createMockRestApiConnector, + createAsyncObservable, + createPaginatedResponse, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('TeamsViewPageComponent', () => { let component: TeamsViewPageComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getTeamById: () => + createAsyncObservable({ id: 'test', name: 'Test Team' }), + getAllUserAccounts: () => + createAsyncObservable(createPaginatedResponse()), + }); + await TestBed.configureTestingModule({ declarations: [TeamsViewPageComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + provideHttpClient(), + provideRouter([]), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + }, + }, + ], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(TeamsViewPageComponent); component = fixture.componentInstance; - fixture.detectChanges(); }); it('should create', () => { diff --git a/src/app/views/dashboard-page/user-accounts-page/user-accounts-page.component.spec.ts b/src/app/views/dashboard-page/user-accounts-page/user-accounts-page.component.spec.ts index d449146f4..149b8e93a 100644 --- a/src/app/views/dashboard-page/user-accounts-page/user-accounts-page.component.spec.ts +++ b/src/app/views/dashboard-page/user-accounts-page/user-accounts-page.component.spec.ts @@ -1,14 +1,33 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; import { UserAccountsPageComponent } from './user-accounts-page.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createMockRestApiConnector, + createAsyncObservable, + createPaginatedResponse, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('UserAccountsPageComponent', () => { let component: UserAccountsPageComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getAllUserAccounts: () => + createAsyncObservable(createPaginatedResponse()), + getAllTeams: () => createAsyncObservable(createPaginatedResponse()), + }); + await TestBed.configureTestingModule({ declarations: [UserAccountsPageComponent], + providers: [ + provideHttpClient(), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); diff --git a/src/app/views/help-page/help-page.component.spec.ts b/src/app/views/help-page/help-page.component.spec.ts index 36b4997d3..83ffe986b 100644 --- a/src/app/views/help-page/help-page.component.spec.ts +++ b/src/app/views/help-page/help-page.component.spec.ts @@ -1,4 +1,9 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { MarkdownModule, MarkdownService } from 'ngx-markdown'; +import { ActivatedRoute } from '@angular/router'; +import { provideHttpClient } from '@angular/common/http'; +import { of } from 'rxjs'; import { HelpPageComponent } from './help-page.component'; @@ -9,6 +14,23 @@ describe('HelpPageComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [HelpPageComponent], + imports: [MarkdownModule.forRoot()], + providers: [ + provideHttpClient(), + MarkdownService, + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + data: of({}), + snapshot: { + data: { markdown: '' }, + }, + }, + }, + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); })); diff --git a/src/app/views/landing-page/landing-page.component.spec.ts b/src/app/views/landing-page/landing-page.component.spec.ts index fbca60c46..81413205e 100644 --- a/src/app/views/landing-page/landing-page.component.spec.ts +++ b/src/app/views/landing-page/landing-page.component.spec.ts @@ -1,4 +1,7 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { provideRouter } from '@angular/router'; import { LandingPageComponent } from './landing-page.component'; @@ -9,6 +12,8 @@ describe('LandingPageComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [LandingPageComponent], + providers: [provideHttpClient(), provideRouter([])], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); })); diff --git a/src/app/views/notes-page/notes-page.component.spec.ts b/src/app/views/notes-page/notes-page.component.spec.ts index 84f1c061a..f4b4599a0 100644 --- a/src/app/views/notes-page/notes-page.component.spec.ts +++ b/src/app/views/notes-page/notes-page.component.spec.ts @@ -1,4 +1,6 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MtxPopoverModule } from '@ng-matero/extensions/popover'; import { NotesPageComponent } from './notes-page.component'; @@ -9,6 +11,8 @@ describe('NotesPageComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [NotesPageComponent], + imports: [MtxPopoverModule], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); diff --git a/src/app/views/profile-page/profile-page.component.spec.ts b/src/app/views/profile-page/profile-page.component.spec.ts index 24da7917a..24a14cb65 100644 --- a/src/app/views/profile-page/profile-page.component.spec.ts +++ b/src/app/views/profile-page/profile-page.component.spec.ts @@ -1,4 +1,13 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { ActivatedRoute } from '@angular/router'; +import { of } from 'rxjs'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { AuthenticationService } from 'src/app/services/connectors/authentication/authentication.service'; +import { + createMockAuthenticationService, + createMockUserAccount, +} from 'src/app/testing/mocks/authentication-service.mock'; import { ProfilePageComponent } from './profile-page.component'; @@ -7,8 +16,32 @@ describe('ProfilePageComponent', () => { let fixture: ComponentFixture; beforeEach(async () => { + const mockAuthService = createMockAuthenticationService({ + currentUser: createMockUserAccount({ + id: 'mock-user-id', + username: 'testuser', + email: 'test@example.com', + }), + canEdit: () => false, + }); + await TestBed.configureTestingModule({ declarations: [ProfilePageComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + provideHttpClient(), + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + }, + }, + { + provide: AuthenticationService, + useValue: mockAuthService, + }, + ], }).compileComponents(); }); diff --git a/src/app/views/reference-manager/reference-manager.component.spec.ts b/src/app/views/reference-manager/reference-manager.component.spec.ts index dc0895538..33bc9fbc6 100644 --- a/src/app/views/reference-manager/reference-manager.component.spec.ts +++ b/src/app/views/reference-manager/reference-manager.component.spec.ts @@ -1,14 +1,32 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; import { ReferenceManagerComponent } from './reference-manager.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createMockRestApiConnector, + createAsyncObservable, + createPaginatedResponse, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('ReferenceManagerComponent', () => { let component: ReferenceManagerComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getAllObjects: () => createAsyncObservable(createPaginatedResponse()), + getAllReferences: () => createAsyncObservable(createPaginatedResponse()), + }); + await TestBed.configureTestingModule({ declarations: [ReferenceManagerComponent], + providers: [ + provideHttpClient(), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); diff --git a/src/app/views/stix/analytic-view/analytic-view.component.html b/src/app/views/stix/analytic-view/analytic-view.component.html index 549ae115b..7ed1ebd25 100644 --- a/src/app/views/stix/analytic-view/analytic-view.component.html +++ b/src/app/views/stix/analytic-view/analytic-view.component.html @@ -18,11 +18,9 @@
diff --git a/src/app/views/stix/analytic-view/analytic-view.component.spec.ts b/src/app/views/stix/analytic-view/analytic-view.component.spec.ts index 8b09ab397..8a0ded251 100644 --- a/src/app/views/stix/analytic-view/analytic-view.component.spec.ts +++ b/src/app/views/stix/analytic-view/analytic-view.component.spec.ts @@ -1,19 +1,36 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { AnalyticViewComponent } from './analytic-view.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createMockRestApiConnector, + createAsyncObservable, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('AnalyticViewComponent', () => { let component: AnalyticViewComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getDefaultMarkingDefinitions: () => createAsyncObservable([]), + getNextAttackId: () => createAsyncObservable('ANA-0001'), + }); + await TestBed.configureTestingModule({ declarations: [AnalyticViewComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + provideHttpClient(), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], }).compileComponents(); fixture = TestBed.createComponent(AnalyticViewComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { mode: 'view', object: {} as any }; }); it('should create', () => { diff --git a/src/app/views/stix/analytic-view/analytic-view.component.ts b/src/app/views/stix/analytic-view/analytic-view.component.ts index 3f01916b7..a505b6eee 100644 --- a/src/app/views/stix/analytic-view/analytic-view.component.ts +++ b/src/app/views/stix/analytic-view/analytic-view.component.ts @@ -28,27 +28,6 @@ export class AnalyticViewComponent extends StixViewPage implements OnInit { ngOnInit(): void { if (this.analytic.firstInitialized) { this.analytic.setDefaultMarkingDefinitions(this.apiService); - this.generateAttackId(); - } - } - - private generateAttackId(): void { - const sub = this.analytic.generateAttackId(this.apiService).subscribe({ - next: val => { - this.analytic.attackID = val; - this.setNameFromAttackId(); - }, - complete: () => sub.unsubscribe(), - }); - } - - public setNameFromAttackId(): void { - if (this.analytic.attackID) { - const regex = /\d+$/; - const match = regex.exec(this.analytic.attackID); - if (match) { - this.analytic.name = `Analytic ${match[0]}`; - } } } } diff --git a/src/app/views/stix/asset-view/asset-view.component.spec.ts b/src/app/views/stix/asset-view/asset-view.component.spec.ts index 735cc3384..bc2a2cd8a 100644 --- a/src/app/views/stix/asset-view/asset-view.component.spec.ts +++ b/src/app/views/stix/asset-view/asset-view.component.spec.ts @@ -1,21 +1,37 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { AssetViewComponent } from './asset-view.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createMockRestApiConnector, + createAsyncObservable, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('AssetViewComponent', () => { let component: AssetViewComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getDefaultMarkingDefinitions: () => createAsyncObservable([]), + }); + await TestBed.configureTestingModule({ declarations: [AssetViewComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + provideHttpClient(), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(AssetViewComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { mode: 'view', object: {} as any }; }); it('should create', () => { diff --git a/src/app/views/stix/campaign-view/campaign-view.component.spec.ts b/src/app/views/stix/campaign-view/campaign-view.component.spec.ts index 8dbf8f66d..8f3003893 100644 --- a/src/app/views/stix/campaign-view/campaign-view.component.spec.ts +++ b/src/app/views/stix/campaign-view/campaign-view.component.spec.ts @@ -1,21 +1,37 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { CampaignViewComponent } from './campaign-view.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createMockRestApiConnector, + createAsyncObservable, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('CampaignViewComponent', () => { let component: CampaignViewComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getDefaultMarkingDefinitions: () => createAsyncObservable([]), + }); + await TestBed.configureTestingModule({ declarations: [CampaignViewComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + provideHttpClient(), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(CampaignViewComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { mode: 'view', object: {} as any }; }); it('should create', () => { diff --git a/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.spec.ts b/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.spec.ts index 4ddaf87b7..32521beac 100644 --- a/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.spec.ts +++ b/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { CollectionImportErrorComponent } from './collection-import-error.component'; @@ -9,12 +10,27 @@ describe('CollectionImportErrorComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [CollectionImportErrorComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(CollectionImportErrorComponent); component = fixture.componentInstance; + component.error = { + bundleErrors: { + duplicateCollection: false, + noCollection: false, + moreThanOneCollection: false, + badlyFormattedCollection: false, + }, + objectErrors: { + summary: { + invalidAttackSpecVersionCount: 0, + duplicateObjectInBundleCount: 0, + }, + }, + }; fixture.detectChanges(); }); diff --git a/src/app/views/stix/collection/collection-import/collection-import-review/collection-import-review.component.spec.ts b/src/app/views/stix/collection/collection-import/collection-import-review/collection-import-review.component.spec.ts index 849455bf1..771d6d8b7 100644 --- a/src/app/views/stix/collection/collection-import/collection-import-review/collection-import-review.component.spec.ts +++ b/src/app/views/stix/collection/collection-import/collection-import-review/collection-import-review.component.spec.ts @@ -1,21 +1,50 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { provideRouter } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { of } from 'rxjs'; import { CollectionImportReviewComponent } from './collection-import-review.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createMockRestApiConnector, + createAsyncObservable, + createPaginatedResponse, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('CollectionImportReviewComponent', () => { let component: CollectionImportReviewComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getAllCollections: () => createAsyncObservable(createPaginatedResponse()), + getAllMarkingDefinitions: () => + createAsyncObservable(createPaginatedResponse()), + }); + await TestBed.configureTestingModule({ declarations: [CollectionImportReviewComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + provideHttpClient(), + provideRouter([]), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + }, + }, + ], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(CollectionImportReviewComponent); component = fixture.componentInstance; - fixture.detectChanges(); }); it('should create', () => { diff --git a/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.html b/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.html index db8f3aab1..9e29dbfbd 100644 --- a/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.html +++ b/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.html @@ -112,7 +112,7 @@ + [phaseProgress]="phaseProgress"> diff --git a/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.spec.ts b/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.spec.ts index 38189342f..e3472f06b 100644 --- a/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.spec.ts +++ b/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.spec.ts @@ -1,21 +1,50 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { provideRouter } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { of } from 'rxjs'; import { CollectionImportComponent } from './collection-import.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createMockRestApiConnector, + createAsyncObservable, + createPaginatedResponse, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('CollectionImportComponent', () => { let component: CollectionImportComponent; let fixture: ComponentFixture; beforeEach(waitForAsync(() => { + const mockRestApiConnector = createMockRestApiConnector({ + getAllCollections: () => createAsyncObservable(createPaginatedResponse()), + getAllMarkingDefinitions: () => + createAsyncObservable(createPaginatedResponse()), + }); + TestBed.configureTestingModule({ declarations: [CollectionImportComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + provideHttpClient(), + provideRouter([]), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + }, + }, + ], }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(CollectionImportComponent); component = fixture.componentInstance; - fixture.detectChanges(); }); it('should create', () => { diff --git a/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts b/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts index b4a16223f..5d66923b1 100644 --- a/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts +++ b/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts @@ -36,6 +36,7 @@ import { AuthenticationService } from 'src/app/services/connectors/authenticatio import { UserAccount } from 'src/app/classes/authn/user-account'; import { logger } from '../../../../../utils/logger'; import { v4 as uuid } from 'uuid'; +import { PhaseProgress } from 'src/app/components/loading-overlay/loading-overlay.component'; import type * as XLSXNS from 'xlsx'; @Component({ @@ -52,6 +53,8 @@ export class CollectionImportComponent implements OnInit { public url = ''; public loadingStep1 = false; public loadingStep2 = false; + public importProgress = 0; // 0-100 (legacy) + public phaseProgress: PhaseProgress[] = []; // Multi-phase progress public select: SelectionModel; // ids of objects which have changed (object-version not already in knowledge base) public changed_ids: string[] = []; @@ -822,27 +825,106 @@ export class CollectionImportComponent implements OnInit { } newBundle.objects = objects; const force = this.import_errors ? true : false; // force import if the collection bundle has errors + + // Use streaming import for progress updates + this.importProgress = 0; + // Initialize multi-phase progress tracking + this.phaseProgress = [ + { + phase: 'processing', + label: 'Processing Objects', + progress: 0, + active: false, + }, + { + phase: 'references', + label: 'Importing References', + progress: 0, + active: false, + }, + { + phase: 'saving', + label: 'Saving Collection', + progress: 0, + active: false, + }, + ]; + logger.log('Starting streaming import...'); const subscription = this.restAPIConnectorService - .postCollectionBundle(newBundle, false, force) + .streamCollectionBundleImport(newBundle, force) .subscribe({ - next: results => { - if (results.import_categories.errors.length > 0) { - logger.warn( - 'Collection import completed with errors:', - results.import_categories.errors + next: event => { + logger.log('SSE Event received:', event.type, event.data); + if (event.type === 'progress') { + // Update phase progress + const phase = event.data.phase; + const phasePercentage = event.data.phasePercentage || 0; + + // Find and update the appropriate phase + const phaseIndex = this.phaseProgress.findIndex( + p => p.phase === phase ); + if (phaseIndex !== -1) { + // Mark all previous phases as complete + for (let i = 0; i < phaseIndex; i++) { + this.phaseProgress[i].progress = 100; + this.phaseProgress[i].active = false; + } + // Update current phase + this.phaseProgress[phaseIndex].progress = phasePercentage; + this.phaseProgress[phaseIndex].active = true; + // Keep future phases at 0 + for ( + let i = phaseIndex + 1; + i < this.phaseProgress.length; + i++ + ) { + this.phaseProgress[i].progress = 0; + this.phaseProgress[i].active = false; + } + } + + logger.log( + 'Phase progress updated:', + phase, + phasePercentage + ); + } else if (event.type === 'complete') { + // Import complete - mark all phases as 100% + this.phaseProgress.forEach(p => { + p.progress = 100; + p.active = false; + }); + logger.log( + 'Import complete, waiting 1 second before transitioning to done' + ); + // Wait 1 second to show all progress bars at 100% before transitioning + setTimeout(() => { + this.handleImportSuccess(new Collection(event.data)); + }, 1000); } - this.save_errors = results.import_categories.errors; - const save_error_ids = new Set( - this.save_errors.map(err => err['object_ref']) - ); - for (const category in results.import_categories) { - if (category == 'errors') continue; - for (const id of results.import_categories[category]) - if (!save_error_ids.has(id)) - this.successfully_saved.add(id); + }, + error: error => { + // Check if it's a timeout error (504 Gateway Timeout or network timeout) + if (error.status === 504 || error.status === 0) { + logger.warn( + 'Import request timed out, but import may still be processing. Starting polling...' + ); + // Import is likely still running - poll for completion + this.pollForImportCompletion( + newBundle.objects[0]?.id || newBundle.collection?.id + ); + } else { + // Real error - show it and stop loading + this.loadingStep2 = false; + this.importProgress = 0; + logger.error('Import failed with error:', error); + this.snackbar.open( + `Import failed: ${error.error?.message || error.message || 'Unknown error'}`, + 'Dismiss', + { duration: 10000 } + ); } - this.stepper.next(); }, complete: () => { subscription.unsubscribe(); @@ -857,6 +939,119 @@ export class CollectionImportComponent implements OnInit { }); } + /** + * Handle successful import results + */ + private handleImportSuccess(results: Collection): void { + if (results.import_categories.errors.length > 0) { + logger.warn( + 'Collection import completed with errors:', + results.import_categories.errors + ); + } + this.save_errors = results.import_categories.errors; + const save_error_ids = new Set( + this.save_errors.map(err => err['object_ref']) + ); + for (const category in results.import_categories) { + if (category == 'errors') continue; + for (const id of results.import_categories[category]) + if (!save_error_ids.has(id)) this.successfully_saved.add(id); + } + this.loadingStep2 = false; + this.stepper.next(); + } + + /** + * Poll for import completion when the initial request times out + */ + private pollForImportCompletion(_expectedCollectionId: string): void { + // Show informative message to user + this.snackbar.open( + 'Large import detected. Processing may take several minutes. Checking for completion...', + 'Dismiss', + { duration: 8000 } + ); + + // Poll every 5 seconds for up to 15 minutes + const maxAttempts = 180; // 15 minutes (180 * 5 seconds) + let attempts = 0; + + const pollInterval = setInterval(() => { + attempts++; + + // Get all collections and find the one that was just imported + this.restAPIConnectorService.getAllCollections().subscribe({ + next: result => { + // Look for a recently modified collection that matches our import + // Since we don't have the exact collection ID before import, we look for + // the most recently modified collection + if (result && result.data && result.data.length > 0) { + // Cast to Collection array since getAllCollections returns collections + const collections = result.data as Collection[]; + + // Sort by modified date descending + const sortedCollections = [...collections].sort( + (a: Collection, b: Collection) => { + const dateA = new Date(a.modified).getTime(); + const dateB = new Date(b.modified).getTime(); + return dateB - dateA; + } + ); + + // Check if the most recent collection was modified within the last few minutes + const mostRecent = sortedCollections[0]; + const modifiedTime = new Date(mostRecent.modified).getTime(); + const now = Date.now(); + const timeDiff = now - modifiedTime; + + // If modified within last 5 minutes, assume it's our import + if (timeDiff < 300000) { + // 5 minutes in milliseconds + clearInterval(pollInterval); + logger.log( + 'Import detected as complete. Collection:', + mostRecent + ); + + // The collection from getAllCollections includes import_categories + // so we can use it directly + this.snackbar.open('Import completed successfully!', 'Dismiss', { + duration: 5000, + }); + this.handleImportSuccess(mostRecent); + } + } + + // Check if we've exceeded max attempts + if (attempts >= maxAttempts) { + clearInterval(pollInterval); + this.loadingStep2 = false; + this.snackbar.open( + 'Import status check timed out after 15 minutes. The import may still be processing. Please check the collections page.', + 'Dismiss', + { duration: 15000 } + ); + } + }, + error: () => { + // Only log errors, don't stop polling unless max attempts reached + logger.warn('Error polling for import completion'); + + if (attempts >= maxAttempts) { + clearInterval(pollInterval); + this.loadingStep2 = false; + this.snackbar.open( + 'Unable to verify import completion. Please check the collections page.', + 'Dismiss', + { duration: 10000 } + ); + } + }, + }); + }, 5000); // Poll every 5 seconds + } + /** * Cancel the collection import and revert to previous step */ diff --git a/src/app/views/stix/collection/collection-index/collection-index-import/collection-index-import.component.spec.ts b/src/app/views/stix/collection/collection-index/collection-index-import/collection-index-import.component.spec.ts index 63ef6e62f..117c24b31 100644 --- a/src/app/views/stix/collection/collection-index/collection-index-import/collection-index-import.component.spec.ts +++ b/src/app/views/stix/collection/collection-index/collection-index-import/collection-index-import.component.spec.ts @@ -1,21 +1,50 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { provideRouter } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { of } from 'rxjs'; import { CollectionIndexImportComponent } from './collection-index-import.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createMockRestApiConnector, + createAsyncObservable, + createPaginatedResponse, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('CollectionIndexImportComponent', () => { let component: CollectionIndexImportComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getAllCollectionIndexes: () => + createAsyncObservable(createPaginatedResponse()), + getAllCollections: () => createAsyncObservable(createPaginatedResponse()), + }); + await TestBed.configureTestingModule({ declarations: [CollectionIndexImportComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + provideHttpClient(), + provideRouter([]), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + }, + }, + ], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(CollectionIndexImportComponent); component = fixture.componentInstance; - fixture.detectChanges(); }); it('should create', () => { diff --git a/src/app/views/stix/collection/collection-index/collection-index-list/collection-index-list.component.spec.ts b/src/app/views/stix/collection/collection-index/collection-index-list/collection-index-list.component.spec.ts index 3b0ced4a7..ac73b399e 100644 --- a/src/app/views/stix/collection/collection-index/collection-index-list/collection-index-list.component.spec.ts +++ b/src/app/views/stix/collection/collection-index/collection-index-list/collection-index-list.component.spec.ts @@ -1,4 +1,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { provideRouter } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { of } from 'rxjs'; import { CollectionIndexListComponent } from './collection-index-list.component'; @@ -9,13 +14,24 @@ describe('CollectionIndexListComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [CollectionIndexListComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + provideHttpClient(), + provideRouter([]), + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + }, + }, + ], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(CollectionIndexListComponent); component = fixture.componentInstance; - fixture.detectChanges(); }); it('should create', () => { diff --git a/src/app/views/stix/collection/collection-index/collection-index-view/collection-index-view.component.spec.ts b/src/app/views/stix/collection/collection-index/collection-index-view/collection-index-view.component.spec.ts index ff41356c7..4c112eb7a 100644 --- a/src/app/views/stix/collection/collection-index/collection-index-view/collection-index-view.component.spec.ts +++ b/src/app/views/stix/collection/collection-index/collection-index-view/collection-index-view.component.spec.ts @@ -1,21 +1,43 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { provideRouter } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { of } from 'rxjs'; import { CollectionIndexViewComponent } from './collection-index-view.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { createMockRestApiConnector } from 'src/app/testing/mocks/rest-api-connector.mock'; describe('CollectionIndexViewComponent', () => { let component: CollectionIndexViewComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({}); + await TestBed.configureTestingModule({ declarations: [CollectionIndexViewComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + provideHttpClient(), + provideRouter([]), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + }, + }, + ], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(CollectionIndexViewComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { mode: 'view', object: {} as any } as any; }); it('should create', () => { diff --git a/src/app/views/stix/collection/collection-list/collection-list.component.spec.ts b/src/app/views/stix/collection/collection-list/collection-list.component.spec.ts index 16cdb37a3..da7ada611 100644 --- a/src/app/views/stix/collection/collection-list/collection-list.component.spec.ts +++ b/src/app/views/stix/collection/collection-list/collection-list.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { CollectionListComponent } from './collection-list.component'; @@ -9,13 +10,18 @@ describe('CollectionListComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [CollectionListComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(CollectionListComponent); component = fixture.componentInstance; - fixture.detectChanges(); + // Set required config input + component.config = { + mode: 'created', + collections: [], + }; }); it('should create', () => { diff --git a/src/app/views/stix/collection/collection-view/collection-view.component.spec.ts b/src/app/views/stix/collection/collection-view/collection-view.component.spec.ts index 06209109e..b70bf325f 100644 --- a/src/app/views/stix/collection/collection-view/collection-view.component.spec.ts +++ b/src/app/views/stix/collection/collection-view/collection-view.component.spec.ts @@ -1,21 +1,44 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { provideRouter } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { of } from 'rxjs'; import { CollectionViewComponent } from './collection-view.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { createMockRestApiConnector } from 'src/app/testing/mocks/rest-api-connector.mock'; describe('CollectionViewComponent', () => { let component: CollectionViewComponent; let fixture: ComponentFixture; beforeEach(waitForAsync(() => { + const mockRestApiConnector = createMockRestApiConnector({}); + TestBed.configureTestingModule({ declarations: [CollectionViewComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + provideHttpClient(), + provideRouter([]), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + snapshot: { data: {}, queryParams: {} }, + }, + }, + ], }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(CollectionViewComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { mode: 'view', object: {} as any }; }); it('should create', () => { diff --git a/src/app/views/stix/data-component-view/data-component-view.component.html b/src/app/views/stix/data-component-view/data-component-view.component.html index d24907f13..7f05e50a8 100644 --- a/src/app/views/stix/data-component-view/data-component-view.component.html +++ b/src/app/views/stix/data-component-view/data-component-view.component.html @@ -62,29 +62,31 @@
- - + }"> +
diff --git a/src/app/views/stix/data-component-view/data-component-view.component.spec.ts b/src/app/views/stix/data-component-view/data-component-view.component.spec.ts index 88f2c431c..c600159ff 100644 --- a/src/app/views/stix/data-component-view/data-component-view.component.spec.ts +++ b/src/app/views/stix/data-component-view/data-component-view.component.spec.ts @@ -1,21 +1,37 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { DataComponentViewComponent } from './data-component-view.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createMockRestApiConnector, + createAsyncObservable, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('DataComponentViewComponent', () => { let component: DataComponentViewComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getDefaultMarkingDefinitions: () => createAsyncObservable([]), + }); + await TestBed.configureTestingModule({ declarations: [DataComponentViewComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + provideHttpClient(), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(DataComponentViewComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { mode: 'view', object: {} as any }; }); it('should create', () => { diff --git a/src/app/views/stix/data-source-view/data-source-view.component.spec.ts b/src/app/views/stix/data-source-view/data-source-view.component.spec.ts index a11ff8d44..0f8b7aeb0 100644 --- a/src/app/views/stix/data-source-view/data-source-view.component.spec.ts +++ b/src/app/views/stix/data-source-view/data-source-view.component.spec.ts @@ -1,20 +1,39 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { DataSourceViewComponent } from './data-source-view.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createMockRestApiConnector, + createAsyncObservable, + createPaginatedResponse, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('DataSourceViewComponent', () => { let component: DataSourceViewComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getAllDataComponents: () => + createAsyncObservable(createPaginatedResponse()), + }); + await TestBed.configureTestingModule({ declarations: [DataSourceViewComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + provideHttpClient(), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(DataSourceViewComponent); component = fixture.componentInstance; + component.config = { mode: 'view', object: {} as any }; fixture.detectChanges(); }); diff --git a/src/app/views/stix/detection-strategy-view/detection-strategy-view.component.spec.ts b/src/app/views/stix/detection-strategy-view/detection-strategy-view.component.spec.ts index a871514bc..18833e3b0 100644 --- a/src/app/views/stix/detection-strategy-view/detection-strategy-view.component.spec.ts +++ b/src/app/views/stix/detection-strategy-view/detection-strategy-view.component.spec.ts @@ -1,19 +1,37 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { DetectionStrategyViewComponent } from './detection-strategy-view.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createMockRestApiConnector, + createAsyncObservable, + createPaginatedResponse, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('DetectionStrategyViewComponent', () => { let component: DetectionStrategyViewComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getDefaultMarkingDefinitions: () => createAsyncObservable([]), + getAllAnalytics: () => createAsyncObservable(createPaginatedResponse()), + }); + await TestBed.configureTestingModule({ declarations: [DetectionStrategyViewComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + provideHttpClient(), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], }).compileComponents(); fixture = TestBed.createComponent(DetectionStrategyViewComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { mode: 'view', object: {} as any }; }); it('should create', () => { diff --git a/src/app/views/stix/group-view/group-view.component.spec.ts b/src/app/views/stix/group-view/group-view.component.spec.ts index e38410583..f40077ff6 100644 --- a/src/app/views/stix/group-view/group-view.component.spec.ts +++ b/src/app/views/stix/group-view/group-view.component.spec.ts @@ -1,21 +1,37 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { GroupViewComponent } from './group-view.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createMockRestApiConnector, + createAsyncObservable, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('GroupViewComponent', () => { let component: GroupViewComponent; let fixture: ComponentFixture; beforeEach(waitForAsync(() => { + const mockRestApiConnector = createMockRestApiConnector({ + getDefaultMarkingDefinitions: () => createAsyncObservable([]), + }); + TestBed.configureTestingModule({ declarations: [GroupViewComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + provideHttpClient(), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(GroupViewComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { mode: 'view', object: {} as any }; }); it('should create', () => { diff --git a/src/app/views/stix/marking-definition-view/marking-definition-view.component.spec.ts b/src/app/views/stix/marking-definition-view/marking-definition-view.component.spec.ts index bf8e653fd..9cbfb44a7 100644 --- a/src/app/views/stix/marking-definition-view/marking-definition-view.component.spec.ts +++ b/src/app/views/stix/marking-definition-view/marking-definition-view.component.spec.ts @@ -1,20 +1,32 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { MarkingDefinitionViewComponent } from './marking-definition-view.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { createMockRestApiConnector } from 'src/app/testing/mocks/rest-api-connector.mock'; describe('MarkingDefinitionViewComponent', () => { let component: MarkingDefinitionViewComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({}); + await TestBed.configureTestingModule({ declarations: [MarkingDefinitionViewComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + provideHttpClient(), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(MarkingDefinitionViewComponent); component = fixture.componentInstance; + component.config = { mode: 'view', object: {} as any }; fixture.detectChanges(); }); diff --git a/src/app/views/stix/matrix/matrix-flat/matrix-flat.component.spec.ts b/src/app/views/stix/matrix/matrix-flat/matrix-flat.component.spec.ts index fe71b729c..ab0b4f6cc 100644 --- a/src/app/views/stix/matrix/matrix-flat/matrix-flat.component.spec.ts +++ b/src/app/views/stix/matrix/matrix-flat/matrix-flat.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { MatrixFlatComponent } from './matrix-flat.component'; @@ -9,6 +10,7 @@ describe('MatrixFlatComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [MatrixFlatComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); })); diff --git a/src/app/views/stix/matrix/matrix-side/matrix-side.component.spec.ts b/src/app/views/stix/matrix/matrix-side/matrix-side.component.spec.ts index 8a2c467fe..5b85d0307 100644 --- a/src/app/views/stix/matrix/matrix-side/matrix-side.component.spec.ts +++ b/src/app/views/stix/matrix/matrix-side/matrix-side.component.spec.ts @@ -1,3 +1,4 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { MatrixSideComponent } from './matrix-side.component'; @@ -8,6 +9,7 @@ describe('MatrixSideComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [MatrixSideComponent], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); })); diff --git a/src/app/views/stix/matrix/matrix-view/matrix-view.component.spec.ts b/src/app/views/stix/matrix/matrix-view/matrix-view.component.spec.ts index 714499eb8..0b5326762 100644 --- a/src/app/views/stix/matrix/matrix-view/matrix-view.component.spec.ts +++ b/src/app/views/stix/matrix/matrix-view/matrix-view.component.spec.ts @@ -1,21 +1,52 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { provideRouter } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { of } from 'rxjs'; import { MatrixViewComponent } from './matrix-view.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createMockRestApiConnector, + createAsyncObservable, + createPaginatedResponse, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('MatrixViewComponent', () => { let component: MatrixViewComponent; let fixture: ComponentFixture; beforeEach(waitForAsync(() => { + const mockRestApiConnector = createMockRestApiConnector({ + getDefaultMarkingDefinitions: () => createAsyncObservable([]), + getTechniquesInMatrix: () => + createAsyncObservable({ tactic_objects: [] }), + getAllTactics: () => createAsyncObservable(createPaginatedResponse()), + }); + TestBed.configureTestingModule({ declarations: [MatrixViewComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + provideHttpClient(), + provideRouter([]), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + }, + }, + ], }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(MatrixViewComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { mode: 'view', object: {} as any }; }); it('should create', () => { diff --git a/src/app/views/stix/mitigation-view/mitigation-view.component.spec.ts b/src/app/views/stix/mitigation-view/mitigation-view.component.spec.ts index 957b04839..eb9ab5d06 100644 --- a/src/app/views/stix/mitigation-view/mitigation-view.component.spec.ts +++ b/src/app/views/stix/mitigation-view/mitigation-view.component.spec.ts @@ -1,21 +1,37 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { MitigationViewComponent } from './mitigation-view.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createMockRestApiConnector, + createAsyncObservable, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('MitigationViewComponent', () => { let component: MitigationViewComponent; let fixture: ComponentFixture; beforeEach(waitForAsync(() => { + const mockRestApiConnector = createMockRestApiConnector({ + getDefaultMarkingDefinitions: () => createAsyncObservable([]), + }); + TestBed.configureTestingModule({ declarations: [MitigationViewComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + provideHttpClient(), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(MitigationViewComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { mode: 'view', object: {} as any }; }); it('should create', () => { diff --git a/src/app/views/stix/relationship-view/relationship-view.component.spec.ts b/src/app/views/stix/relationship-view/relationship-view.component.spec.ts index 94c06f5a9..a114a9c3d 100644 --- a/src/app/views/stix/relationship-view/relationship-view.component.spec.ts +++ b/src/app/views/stix/relationship-view/relationship-view.component.spec.ts @@ -1,21 +1,43 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { provideRouter } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { of } from 'rxjs'; import { RelationshipViewComponent } from './relationship-view.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { createMockRestApiConnector } from 'src/app/testing/mocks/rest-api-connector.mock'; describe('RelationshipViewComponent', () => { let component: RelationshipViewComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({}); + await TestBed.configureTestingModule({ declarations: [RelationshipViewComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + provideHttpClient(), + provideRouter([]), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + }, + }, + ], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(RelationshipViewComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { mode: 'view', object: {} as any }; }); it('should create', () => { diff --git a/src/app/views/stix/software-view/software-view.component.spec.ts b/src/app/views/stix/software-view/software-view.component.spec.ts index e1bedca8c..3a41fe5ab 100644 --- a/src/app/views/stix/software-view/software-view.component.spec.ts +++ b/src/app/views/stix/software-view/software-view.component.spec.ts @@ -1,20 +1,43 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; import { SoftwareViewComponent } from './software-view.component'; +import { Software } from 'src/app/classes/stix/software'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createMockRestApiConnector, + createAsyncObservable, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('SoftwareViewComponent', () => { let component: SoftwareViewComponent; let fixture: ComponentFixture; beforeEach(waitForAsync(() => { + const mockRestApiConnector = createMockRestApiConnector({ + getDefaultMarkingDefinitions: () => createAsyncObservable([]), + }); + TestBed.configureTestingModule({ declarations: [SoftwareViewComponent], + providers: [ + provideHttpClient(), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SoftwareViewComponent); component = fixture.componentInstance; + // Set required config input with a mock Software object + const mockSoftware = new Software('malware'); + component.config = { + mode: 'view', + object: mockSoftware, + }; fixture.detectChanges(); }); diff --git a/src/app/views/stix/stix-dialog/stix-dialog.component.spec.ts b/src/app/views/stix/stix-dialog/stix-dialog.component.spec.ts index 124fa9e6e..398cb93d6 100644 --- a/src/app/views/stix/stix-dialog/stix-dialog.component.spec.ts +++ b/src/app/views/stix/stix-dialog/stix-dialog.component.spec.ts @@ -1,21 +1,54 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { provideRouter } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { of } from 'rxjs'; import { StixDialogComponent } from './stix-dialog.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createAsyncObservable, + createMockRestApiConnector, + createPaginatedResponse, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('StixDialogComponent', () => { let component: StixDialogComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getRelatedTo: () => createAsyncObservable(createPaginatedResponse([])), + }); + await TestBed.configureTestingModule({ declarations: [StixDialogComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + provideHttpClient(), + provideRouter([]), + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + }, + }, + { + provide: MAT_DIALOG_DATA, + useValue: { mode: 'view', object: {} as any }, + }, + { provide: MatDialogRef, useValue: {} }, + ], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(StixDialogComponent); component = fixture.componentInstance; - fixture.detectChanges(); }); it('should create', () => { diff --git a/src/app/views/stix/stix-list-page/stix-list-page.component.spec.ts b/src/app/views/stix/stix-list-page/stix-list-page.component.spec.ts index 752649f0b..e19551616 100644 --- a/src/app/views/stix/stix-list-page/stix-list-page.component.spec.ts +++ b/src/app/views/stix/stix-list-page/stix-list-page.component.spec.ts @@ -1,4 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { StixListPageComponent } from './stix-list-page.component'; @@ -9,11 +11,12 @@ describe('StixListPageComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [StixListPageComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [provideHttpClient()], }).compileComponents(); fixture = TestBed.createComponent(StixListPageComponent); component = fixture.componentInstance; - fixture.detectChanges(); }); it('should create', () => { diff --git a/src/app/views/stix/stix-page/stix-page.component.spec.ts b/src/app/views/stix/stix-page/stix-page.component.spec.ts index 4bb966822..133ab5f9e 100644 --- a/src/app/views/stix/stix-page/stix-page.component.spec.ts +++ b/src/app/views/stix/stix-page/stix-page.component.spec.ts @@ -1,21 +1,63 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { provideRouter } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { of } from 'rxjs'; import { StixPageComponent } from './stix-page.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createAsyncObservable, + createMockRestApiConnector, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('StixPageComponent', () => { let component: StixPageComponent; let fixture: ComponentFixture; beforeEach(async () => { + const mockRestApiConnector = createMockRestApiConnector({ + getAllNotes: () => createAsyncObservable([]), + getSoftware: () => createAsyncObservable([]), + getGroup: () => createAsyncObservable([]), + getCampaign: () => createAsyncObservable([]), + getMatrix: () => createAsyncObservable([]), + getMitigation: () => createAsyncObservable([]), + getTactic: () => createAsyncObservable([]), + getTechnique: () => createAsyncObservable([]), + getCollection: () => createAsyncObservable([]), + getDataSource: () => createAsyncObservable([]), + getDataComponent: () => createAsyncObservable([]), + getDetectionStrategy: () => createAsyncObservable([]), + getAnalytic: () => createAsyncObservable([]), + getAsset: () => createAsyncObservable([]), + getMarkingDefinition: () => createAsyncObservable([]), + deleteCollection: () => createAsyncObservable({}), + }); + await TestBed.configureTestingModule({ declarations: [StixPageComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + provideHttpClient(), + provideRouter([]), + { + provide: ActivatedRoute, + useValue: { + params: of({}), + queryParams: of({}), + }, + }, + ], }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(StixPageComponent); component = fixture.componentInstance; - fixture.detectChanges(); + (component as any).config = { mode: 'view', object: {} as any }; }); it('should create', () => { diff --git a/src/app/views/stix/tactic-view/tactic-view.component.spec.ts b/src/app/views/stix/tactic-view/tactic-view.component.spec.ts index 903177ca2..144c3c433 100644 --- a/src/app/views/stix/tactic-view/tactic-view.component.spec.ts +++ b/src/app/views/stix/tactic-view/tactic-view.component.spec.ts @@ -1,21 +1,38 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TacticViewComponent } from './tactic-view.component'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createMockRestApiConnector, + createAsyncObservable, +} from 'src/app/testing/mocks/rest-api-connector.mock'; describe('TacticViewComponent', () => { let component: TacticViewComponent; let fixture: ComponentFixture; beforeEach(waitForAsync(() => { + const mockRestApiConnector = createMockRestApiConnector({ + getTechniquesInTactic: () => createAsyncObservable([]), + getDefaultMarkingDefinitions: () => createAsyncObservable([]), + }); + TestBed.configureTestingModule({ declarations: [TacticViewComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + provideHttpClient(), + { provide: RestApiConnectorService, useValue: mockRestApiConnector }, + ], }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(TacticViewComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { mode: 'view', object: {} as any }; }); it('should create', () => { diff --git a/src/app/views/stix/technique-view/technique-view.component.spec.ts b/src/app/views/stix/technique-view/technique-view.component.spec.ts index ebc049bd4..21601d24d 100644 --- a/src/app/views/stix/technique-view/technique-view.component.spec.ts +++ b/src/app/views/stix/technique-view/technique-view.component.spec.ts @@ -1,21 +1,46 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { provideHttpClient } from '@angular/common/http'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TechniqueViewComponent } from './technique-view.component'; +import { AuthenticationService } from 'src/app/services/connectors/authentication/authentication.service'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { + createMockRestApiConnector, + createAsyncObservable, + createPaginatedResponse, +} from 'src/app/testing/mocks/rest-api-connector.mock'; +import { createMockAuthenticationService } from 'src/app/testing/mocks/authentication-service.mock'; +import { Role } from 'src/app/classes/authn/role'; describe('TechniqueViewComponent', () => { let component: TechniqueViewComponent; let fixture: ComponentFixture; beforeEach(waitForAsync(() => { + const mockAuthService = createMockAuthenticationService({ + isAuthorized: () => true, + }); + const mockRestApiService = createMockRestApiConnector({ + getAllMarkingDefinitions: () => + createAsyncObservable(createPaginatedResponse()), + }); + TestBed.configureTestingModule({ declarations: [TechniqueViewComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + provideHttpClient(), + { provide: AuthenticationService, useValue: mockAuthService }, + { provide: RestApiConnectorService, useValue: mockRestApiService }, + ], }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(TechniqueViewComponent); component = fixture.componentInstance; - fixture.detectChanges(); + component.config = { mode: 'view', object: {} as any }; }); it('should create', () => { diff --git a/src/style/colors.scss b/src/style/colors.scss index 7dc530857..f71168e5f 100644 --- a/src/style/colors.scss +++ b/src/style/colors.scss @@ -26,6 +26,10 @@ $colors: ( color: $mitre-navy, on-color: $mitre-silver, ), + primary-dark: ( + color: $mitre-blue, + on-color: $mitre-silver, + ), secondary: ( color: $mitre-blue, on-color: $mitre-black, diff --git a/src/style/layouts/app-table.scss b/src/style/layouts/app-table.scss index 327ce77e8..ffa4fea66 100644 --- a/src/style/layouts/app-table.scss +++ b/src/style/layouts/app-table.scss @@ -2,10 +2,8 @@ @use '../typography'; @use '@angular/material' as mat; -// @import "../globals"; .app-table { - // border-collapse: collapse; border-spacing: 0px; .light & { @@ -34,3 +32,27 @@ padding: 12px 12px; } } + +.property-table { + width: 100%; + padding: 8px; + .light & { + background-color: colors.color(light); + } + .dark & { + background-color: colors.color(dark); + } + tr.mat-mdc-header-row { + height: 25px; + } + .mat-mdc-cell, + .mat-mdc-header-cell { + padding: 0 10px; + .light & { + border-color: colors.border-color(light); + } + .dark & { + border-color: colors.border-color(dark); + } + } +} diff --git a/src/style/layouts/extended-button.scss b/src/style/layouts/extended-button.scss index bb3af6ad8..8abd16884 100644 --- a/src/style/layouts/extended-button.scss +++ b/src/style/layouts/extended-button.scss @@ -33,6 +33,31 @@ } } +.table-icon-button { + @extend .small-icon-button; + width: 20px !important; + height: 20px !important; + + & > *[role='img'] { + width: 20px; + height: 20px; + font-size: 20px; + + svg { + width: 20px; + height: 20px; + } + } + + .mat-mdc-button-touch-target { + width: 20px !important; + height: 20px !important; + } +} +.table-icon-button + .table-icon-button { + margin-left: 8px; +} + .mini-icon-button { @extend .small-icon-button; width: 20px !important; diff --git a/src/style/theme.scss b/src/style/theme.scss index 002de0e83..836476339 100644 --- a/src/style/theme.scss +++ b/src/style/theme.scss @@ -54,7 +54,7 @@ $dark-theme: mat.m2-define-dark-theme( ( color: ( primary: mat.m2-define-palette( - colors.to-material-map(primary), + colors.to-material-map(primary-dark), 50, 100, 200 diff --git a/src/test-setup.ts b/src/test-setup.ts new file mode 100644 index 000000000..d713883ad --- /dev/null +++ b/src/test-setup.ts @@ -0,0 +1,27 @@ +// This file is required by vitest and loads the Angular testing environment + +import '@angular/compiler'; +import '@analogjs/vitest-angular/setup-zone'; + +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting, +} from '@angular/platform-browser-dynamic/testing'; + +import { initLogger } from './app/utils/logger'; + +// Initialize a dummy logger for all tests +initLogger({ + log: console.log, + error: console.error, +} as any); + +// Initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting(), + { + teardown: { destroyAfterEach: false }, + } +); diff --git a/src/test.ts b/src/test.ts deleted file mode 100644 index 1886cdad9..000000000 --- a/src/test.ts +++ /dev/null @@ -1,17 +0,0 @@ -// This file is required by karma.conf.js and loads recursively all the .spec and framework files - -import 'zone.js/testing'; -import { getTestBed } from '@angular/core/testing'; -import { - BrowserDynamicTestingModule, - platformBrowserDynamicTesting, -} from '@angular/platform-browser-dynamic/testing'; - -// First, initialize the Angular testing environment. -getTestBed().initTestEnvironment( - BrowserDynamicTestingModule, - platformBrowserDynamicTesting(), - { - teardown: { destroyAfterEach: false }, - } -); diff --git a/tsconfig.spec.json b/tsconfig.spec.json index 092345b02..2de449227 100644 --- a/tsconfig.spec.json +++ b/tsconfig.spec.json @@ -4,12 +4,14 @@ "compilerOptions": { "outDir": "./out-tsc/spec", "types": [ - "jasmine" + "vitest/globals", + "node" ] }, "files": [ - "src/test.ts", - "src/polyfills.ts" + "src/test-setup.ts", + "src/polyfills.ts", + "vite.config.ts" ], "include": [ "src/**/*.spec.ts", diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 000000000..d55b37364 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,45 @@ +/// +import angular from '@analogjs/vite-plugin-angular'; +import { defineConfig } from 'vite'; +import path from 'path'; + +// https://vite.dev/config/ +export default defineConfig(({ mode }) => ({ + plugins: [angular()], + build: { + sourcemap: true, + }, + test: { + globals: true, + dir: 'src', + setupFiles: ['src/test-setup.ts'], + environment: 'jsdom', + maxConcurrency: 1, + bail: 10, // Stop after 10 test failures + coverage: { + provider: 'v8', + reporter: ['text', 'text-summary', 'html', 'lcov'], + reportsDirectory: 'coverage', + exclude: [ + '**/*.config.*', + 'node_modules/', + '.nuxt/', + 'dist/', + 'src/environments/', + 'src/app/testing/mocks/', + ], + all: false, + clean: true, + }, + }, + resolve: { + conditions: ['default', 'node'], + alias: { + src: path.resolve(__dirname, './src'), + 'package.json': path.resolve(__dirname, './package.json'), + }, + }, + define: { + 'import.meta.vitest': mode !== 'production', + }, +})); From dc3203a4231ebf3c3f7f01a6609a7fb7d302941e Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Tue, 24 Mar 2026 13:41:30 -0400 Subject: [PATCH 002/190] feat(release-tracks): add release track type definitions and data models --- .../classes/release-tracks/component-track.ts | 34 +++ src/app/classes/release-tracks/composition.ts | 30 ++ src/app/classes/release-tracks/config.ts | 39 +++ src/app/classes/release-tracks/enums.ts | 199 ++++++++++++ src/app/classes/release-tracks/history.ts | 15 + src/app/classes/release-tracks/index.ts | 8 + src/app/classes/release-tracks/registry.ts | 24 ++ src/app/classes/release-tracks/snapshot.ts | 283 ++++++++++++++++++ src/app/classes/release-tracks/tiers.ts | 31 ++ 9 files changed, 663 insertions(+) create mode 100644 src/app/classes/release-tracks/component-track.ts create mode 100644 src/app/classes/release-tracks/composition.ts create mode 100644 src/app/classes/release-tracks/config.ts create mode 100644 src/app/classes/release-tracks/enums.ts create mode 100644 src/app/classes/release-tracks/history.ts create mode 100644 src/app/classes/release-tracks/index.ts create mode 100644 src/app/classes/release-tracks/registry.ts create mode 100644 src/app/classes/release-tracks/snapshot.ts create mode 100644 src/app/classes/release-tracks/tiers.ts diff --git a/src/app/classes/release-tracks/component-track.ts b/src/app/classes/release-tracks/component-track.ts new file mode 100644 index 000000000..28d798b18 --- /dev/null +++ b/src/app/classes/release-tracks/component-track.ts @@ -0,0 +1,34 @@ +// ----------------------------------------------------------------------------- +// Component Tracks +// +// Release track (standard or virtual) referenced by a virtual release track +// ----------------------------------------------------------------------------- + +import { ResolutionStrategyType } from './enums'; + +export interface ComponentTrackFilters { + object_types?: string[]; + domains?: string[]; +} + +export interface ComponentTrack { + track_id: string; + resolution_strategy: ResolutionStrategyType; + priority: number; + version?: string | null; + snapshot?: Date; + filters?: ComponentTrackFilters; +} + +export interface ComponentSnapshotResolution { + track_id: string; + track_name: string; + track_type: string; + resolved_snapshot_id: Date; + resolved_version?: string | null; + strategy_used: string; + filters_applied?: ComponentTrackFilters; + total_objects_in_source: number; + objects_after_filter: number; + objects_contributed: number; +} diff --git a/src/app/classes/release-tracks/composition.ts b/src/app/classes/release-tracks/composition.ts new file mode 100644 index 000000000..2e953b36f --- /dev/null +++ b/src/app/classes/release-tracks/composition.ts @@ -0,0 +1,30 @@ +// ----------------------------------------------------------------------------- +// Composition +// +// Rules and configuration that defines how a virtual release track +// aggregates content from component tracks +// ----------------------------------------------------------------------------- + +import { DeduplicationStrategyType } from './enums'; +import { ComponentTrack, ComponentSnapshotResolution } from './component-track'; + +export interface Composition { + component_tracks?: ComponentTrack[]; + deduplication?: { + strategy?: DeduplicationStrategyType; + }; +} + +export interface DeduplicationReport { + total_objects_before?: number; + total_objects_after?: number; + duplicates_found?: number; + conflicts_resolved?: any[]; +} + +export interface CompositionResolution { + resolved_at?: Date; + component_snapshots?: ComponentSnapshotResolution[]; + deduplication?: DeduplicationReport; + summary?: any; +} diff --git a/src/app/classes/release-tracks/config.ts b/src/app/classes/release-tracks/config.ts new file mode 100644 index 000000000..2e84353e1 --- /dev/null +++ b/src/app/classes/release-tracks/config.ts @@ -0,0 +1,39 @@ +// ----------------------------------------------------------------------------- +// Release Track Configuration +// ----------------------------------------------------------------------------- + +import { + ConflictPolicyType, + WorkflowStatusType, + MemberSyncBehaviorType, + MemberSyncPolicyType, + MemberSyncStrategyType, +} from './enums'; + +export interface PromotionConflicts { + candidates_to_staged?: ConflictPolicyType; + staged_to_members?: ConflictPolicyType; +} + +export interface IncludeSecondaryObjects { + enabled?: boolean; + status_threshold?: WorkflowStatusType; +} + +export interface MemberSyncSupplant { + behavior?: MemberSyncBehaviorType; + status_policy?: MemberSyncPolicyType; +} + +export interface MemberSync { + strategy?: MemberSyncStrategyType; + supplant?: MemberSyncSupplant; +} + +export interface ReleaseTrackConfig { + candidacy_threshold?: WorkflowStatusType; + auto_promote?: boolean; + include_secondary_objects?: IncludeSecondaryObjects; + promotion_conflicts?: PromotionConflicts; + member_sync?: MemberSync; +} diff --git a/src/app/classes/release-tracks/enums.ts b/src/app/classes/release-tracks/enums.ts new file mode 100644 index 000000000..88a7e1ba1 --- /dev/null +++ b/src/app/classes/release-tracks/enums.ts @@ -0,0 +1,199 @@ +// ----------------------------------------------------------------------------- +// Release Track Type +// ----------------------------------------------------------------------------- + +export enum ReleaseTrack { + Standard = 'standard', + Virtual = 'virtual', +} + +export type ReleaseTrackType = ReleaseTrack.Standard | ReleaseTrack.Virtual; + +export const RELEASE_TRACK_TYPE_OPTIONS: ReleaseTrackType[] = Object.values( + ReleaseTrack +) as ReleaseTrackType[]; + +// ----------------------------------------------------------------------------- +// Conflict Resolution +// ----------------------------------------------------------------------------- + +export enum ConflictPolicy { + AlwaysOverwrite = 'always_overwrite', // replace with incoming entry + AlwaysReject = 'always_reject', // always reject incoming + PreferLatest = 'prefer_latest', // keep whichever has newer object_modified + Abort = 'abort', // throw error on any conflict +} + +export type ConflictPolicyType = + | ConflictPolicy.AlwaysOverwrite + | ConflictPolicy.AlwaysReject + | ConflictPolicy.PreferLatest + | ConflictPolicy.Abort; + +export const CONFLICT_POLICY_OPTIONS: ConflictPolicyType[] = Object.values( + ConflictPolicy +) as ConflictPolicyType[]; + +// ----------------------------------------------------------------------------- +// Deduplication Strategy +// ----------------------------------------------------------------------------- + +export enum DeduplicationStrategy { + PrioritizeLatestObject = 'prioritize_latest_object', // keep version with newest object_modified + PrioritizeLatestSnapshot = 'prioritize_latest_snapshot', // keep version from most recently modified snapshot + PrioritizeHigherPriority = 'prioritize_higher_priority', // keep version from the higher-priority conmponent (lower number) + Quarantine = 'quarantine', // send all conflicting versions to quarantine for manual review +} + +export type DeduplicationStrategyType = + | DeduplicationStrategy.PrioritizeLatestObject + | DeduplicationStrategy.PrioritizeLatestSnapshot + | DeduplicationStrategy.PrioritizeHigherPriority + | DeduplicationStrategy.Quarantine; + +export const DEDUPLICATION_STRATEGY_OPTIONS: DeduplicationStrategyType[] = + Object.values(DeduplicationStrategy) as DeduplicationStrategyType[]; + +// ----------------------------------------------------------------------------- +// Export Format +// ----------------------------------------------------------------------------- + +export enum ExportFormat { + Snapshot = 'snapshot', + Bundle = 'bundle', + Workbench = 'workbench', + FileSystemStore = 'filesystemstore', +} + +export type ExportFormatType = + | ExportFormat.Snapshot + | ExportFormat.Bundle + | ExportFormat.Workbench + | ExportFormat.FileSystemStore; + +export const EXPORT_FORMAT_OPTIONS: ExportFormatType[] = Object.values( + ExportFormat +) as ExportFormatType[]; + +// ----------------------------------------------------------------------------- +// Release Track Snapshot Tiers +// ----------------------------------------------------------------------------- + +export enum SnapshotTier { + Member = 'released', + Staged = 'staged', + Candidate = 'candidate', + All = 'all', +} + +export type SnapshotTierType = + | SnapshotTier.Member + | SnapshotTier.Staged + | SnapshotTier.Candidate + | SnapshotTier.All; + +export const SNAPSHOT_TIER_OPTIONS: SnapshotTierType[] = Object.values( + SnapshotTier +) as SnapshotTierType[]; + +// ----------------------------------------------------------------------------- +// Workflow Statuses +// ----------------------------------------------------------------------------- + +export enum WorkflowStatus { + WorkInProgress = 'work-in-progress', + AwaitingReview = 'awaiting-review', + Reviewed = 'reviewed', +} + +export type WorkflowStatusType = + | WorkflowStatus.WorkInProgress + | WorkflowStatus.AwaitingReview + | WorkflowStatus.Reviewed; + +// ----------------------------------------------------------------------------- +// Candidacy Thresholds +// ----------------------------------------------------------------------------- + +export const CANDIDACY_THRESHOLD_OPTIONS: WorkflowStatusType[] = Object.values( + WorkflowStatus +) as WorkflowStatusType[]; + +// ----------------------------------------------------------------------------- +// Resolution Strategy +// ----------------------------------------------------------------------------- + +export enum ResolutionStrategy { + LatestTagged = 'latest_tagged', + SpecificVersion = 'specific_version', + SpecificSnapshot = 'specific_snapshot', +} + +export type ResolutionStrategyType = + | ResolutionStrategy.LatestTagged + | ResolutionStrategy.SpecificVersion + | ResolutionStrategy.SpecificSnapshot; + +export const RESOLUTION_STRATEGY_OPTIONS: ResolutionStrategyType[] = + Object.values(ResolutionStrategy) as ResolutionStrategyType[]; + +// ----------------------------------------------------------------------------- +// Snapshot Schedule Modes +// ----------------------------------------------------------------------------- + +export enum SnapshotScheduleMode { + Manual = 'manual', + Cron = 'cron', + Dates = 'dates', +} + +export type SnapshotScheduleModeType = + | SnapshotScheduleMode.Manual + | SnapshotScheduleMode.Cron + | SnapshotScheduleMode.Dates; + +export const SNAPSHOT_MODE_OPTIONS: SnapshotScheduleModeType[] = Object.values( + SnapshotScheduleMode +) as SnapshotScheduleModeType[]; + +// ----------------------------------------------------------------------------- +// Member Sync +// ----------------------------------------------------------------------------- + +export enum MemberSyncStrategy { + TrackLatest = 'track_latest', + Manual = 'manual', +} + +export type MemberSyncStrategyType = + | MemberSyncStrategy.TrackLatest + | MemberSyncStrategy.Manual; + +export const MEMBER_SYNC_STRATEGY_OPTIONS: MemberSyncStrategyType[] = + Object.values(MemberSyncStrategy) as MemberSyncStrategyType[]; + +export enum MemberSyncBehavior { + Replace = 'replace', + Queue = 'queue', + Ignore = 'ignore', +} + +export type MemberSyncBehaviorType = + | MemberSyncBehavior.Replace + | MemberSyncBehavior.Queue + | MemberSyncBehavior.Ignore; + +export const MEMBER_SYNC_BEHAVIOR_OPTIONS: MemberSyncBehaviorType[] = + Object.values(MemberSyncBehavior) as MemberSyncBehaviorType[]; + +export enum MemberSyncPolicy { + Reset = 'reset', + Preserve = 'preserve', +} + +export type MemberSyncPolicyType = + | MemberSyncPolicy.Reset + | MemberSyncPolicy.Preserve; + +export const MEMBER_SYNC_STATUS_POLICY_OPTIONS: MemberSyncPolicyType[] = + Object.values(MemberSyncPolicy) as MemberSyncPolicyType[]; diff --git a/src/app/classes/release-tracks/history.ts b/src/app/classes/release-tracks/history.ts new file mode 100644 index 000000000..870e2f9a4 --- /dev/null +++ b/src/app/classes/release-tracks/history.ts @@ -0,0 +1,15 @@ +export interface VersionHistorySummary { + members_count?: number; + promoted_count?: number; + staged_count?: number; + candidate_count?: number; +} + +export interface VersionHistoryEntry { + version: string; + tagged_at: Date; + tagged_by: string; + snapshot_id: Date; + summary?: VersionHistorySummary; + component_versions?: any; // virtual tracks only +} diff --git a/src/app/classes/release-tracks/index.ts b/src/app/classes/release-tracks/index.ts new file mode 100644 index 000000000..9a2b0e5cf --- /dev/null +++ b/src/app/classes/release-tracks/index.ts @@ -0,0 +1,8 @@ +export * from './component-track'; +export * from './composition'; +export * from './config'; +export * from './enums'; +export * from './history'; +export * from './registry'; +export * from './snapshot'; +export * from './tiers'; diff --git a/src/app/classes/release-tracks/registry.ts b/src/app/classes/release-tracks/registry.ts new file mode 100644 index 000000000..52308527a --- /dev/null +++ b/src/app/classes/release-tracks/registry.ts @@ -0,0 +1,24 @@ +import { ReleaseTrackType, SnapshotScheduleModeType } from './enums'; + +export interface ReleaseTrackRegistry { + track_id: string; + type: ReleaseTrackType; + name: string; + description?: string; + created_at: Date; + updated_at: Date; + + latest_snapshot_modified?: Date | null; + latest_tagged_version?: string | null; + snapshot_count?: number; + tagged_release_count?: number; + + // virtual tracks only + snapshot_schedule?: SnapshotSchedule; +} + +export interface SnapshotSchedule { + mode?: SnapshotScheduleModeType; + cron?: string | null; + dates?: Date[] | undefined; +} diff --git a/src/app/classes/release-tracks/snapshot.ts b/src/app/classes/release-tracks/snapshot.ts new file mode 100644 index 000000000..e2e5850e4 --- /dev/null +++ b/src/app/classes/release-tracks/snapshot.ts @@ -0,0 +1,283 @@ +import { Composition, CompositionResolution } from './composition'; +import { ReleaseTrackConfig } from './config'; +import { ReleaseTrack, ReleaseTrackType } from './enums'; +import { VersionHistoryEntry } from './history'; +import { + CandidateEntry, + MemberEntry, + QuarantineEntry, + StagedEntry, +} from './tiers'; + +export class ReleaseTrackSnapshot { + public id = ''; + public type: ReleaseTrackType; + public modified: Date = new Date(); + public version?: string | null; + public name = ''; + public description?: string; + public created: Date = new Date(); + public created_by_ref?: string; + public object_marking_refs?: string[]; + + public config: ReleaseTrackConfig = {} as ReleaseTrackConfig; + public version_history: VersionHistoryEntry[] = []; + + // standard track tiers + public members: MemberEntry[] = []; + public staged?: StagedEntry[]; + public candidates?: CandidateEntry[]; + + // virtual track tiers + public quarantine?: QuarantineEntry[]; + + // virtual track composition + public composition?: Composition; + public composition_resolution?: CompositionResolution; + + constructor(raw?: any) { + if (raw) this.deserialize(raw); + } + + public get isVirtual(): boolean { + return this.type === ReleaseTrack.Virtual; + } + + public get isStandard(): boolean { + return this.type === ReleaseTrack.Standard; + } + + public get memberCount(): number { + return this.members ? this.members.length : 0; + } + + public get stagedCount(): number { + return this.staged ? this.staged.length : 0; + } + + public get candidateCount(): number { + return this.candidates ? this.candidates.length : 0; + } + + public get quarantineCount(): number { + return this.quarantine ? this.quarantine.length : 0; + } + + public get isTagged(): boolean { + return !!this.version; + } + + public get latestTaggedVersion(): string | null { + if (!this.version_history || this.version_history.length === 0) return null; + const sorted = [...this.version_history].sort((a, b) => { + const ta = a.tagged_at ? new Date(a.tagged_at).getTime() : 0; + const tb = b.tagged_at ? new Date(b.tagged_at).getTime() : 0; + return tb - ta; + }); + return sorted[0].version || null; + } + + // Populate release track snapshot fields from a raw object + public deserialize(raw: any) { + if (!raw) return; + + if ('id' in raw) this.id = raw.id; + if ('type' in raw) this.type = raw.type; + if ('modified' in raw) this.modified = new Date(raw.modified); + if ('version' in raw) this.version = raw.version; + if ('name' in raw) this.name = raw.name; + if ('description' in raw) this.description = raw.description; + if ('created' in raw) this.created = new Date(raw.created); + if ('created_by_ref' in raw) this.created_by_ref = raw.created_by_ref; + if ('object_marking_refs' in raw && Array.isArray(raw.object_marking_refs)) + this.object_marking_refs = raw.object_marking_refs.slice(); + + if ('config' in raw) this.config = raw.config; + + if ('version_history' in raw && Array.isArray(raw.version_history)) { + this.version_history = raw.version_history.map((v: any) => { + const entry: VersionHistoryEntry = { ...v } as any; + if (v.tagged_at) entry.tagged_at = new Date(v.tagged_at); + if (v.snapshot_id) entry.snapshot_id = new Date(v.snapshot_id); + return entry; + }); + } + + if ('members' in raw && Array.isArray(raw.members)) { + this.members = raw.members.map((m: any) => ({ + object_ref: m.object_ref, + object_modified: new Date(m.object_modified), + })); + } + + if ('staged' in raw && Array.isArray(raw.staged)) { + this.staged = raw.staged.map((s: any) => ({ + object_ref: s.object_ref, + object_modified: new Date(s.object_modified), + object_status: s.object_status, + object_staged_at: s.object_staged_at + ? new Date(s.object_staged_at) + : undefined, + object_staged_by: s.object_staged_by, + })); + } + + if ('candidates' in raw && Array.isArray(raw.candidates)) { + this.candidates = raw.candidates.map((c: any) => ({ + object_ref: c.object_ref, + object_modified: new Date(c.object_modified), + object_status: c.object_status, + object_added_at: c.object_added_at + ? new Date(c.object_added_at) + : undefined, + object_added_by: c.object_added_by, + })); + } + + if ('quarantine' in raw && Array.isArray(raw.quarantine)) { + this.quarantine = raw.quarantine.map((q: any) => ({ + object_ref: q.object_ref, + object_modified: new Date(q.object_modified), + source_track_id: q.source_track_id, + source_track_name: q.source_track_name, + source_snapshot_version: q.source_snapshot_version, + conflict_reason: q.conflict_reason, + })); + } + + if ('composition' in raw) this.composition = raw.composition; + + if ('composition_resolution' in raw && raw.composition_resolution) { + const cr = raw.composition_resolution; + const resolved: CompositionResolution = { ...cr } as any; + if (cr.resolved_at) resolved.resolved_at = new Date(cr.resolved_at); + if (cr.component_snapshots && Array.isArray(cr.component_snapshots)) { + resolved.component_snapshots = cr.component_snapshots.map( + (cs: any) => ({ + ...cs, + resolved_snapshot_id: cs.resolved_snapshot_id + ? new Date(cs.resolved_snapshot_id) + : undefined, + }) + ); + } + this.composition_resolution = resolved; + } + } + + // Generate object representation of the release track snapshot + public serialize(): any { + return { + id: this.id, + type: this.type, + modified: this.modified ? this.modified.toISOString() : undefined, + version: this.version, + name: this.name, + description: this.description, + created: this.created ? this.created.toISOString() : undefined, + created_by_ref: this.created_by_ref, + object_marking_refs: this.object_marking_refs, + config: this.config, + version_history: this.version_history?.map(v => ({ + ...v, + tagged_at: v.tagged_at + ? (v.tagged_at as any).toISOString() + : v.tagged_at, + snapshot_id: v.snapshot_id + ? (v.snapshot_id as any).toISOString() + : v.snapshot_id, + })), + members: this.members?.map(m => ({ + ...m, + object_modified: m.object_modified + ? (m.object_modified as any).toISOString() + : m.object_modified, + })), + staged: this.staged?.map(s => ({ + ...s, + object_modified: s.object_modified + ? (s.object_modified as any).toISOString() + : s.object_modified, + object_staged_at: s.object_staged_at + ? (s.object_staged_at as any).toISOString() + : s.object_staged_at, + })), + candidates: this.candidates?.map(c => ({ + ...c, + object_modified: c.object_modified + ? (c.object_modified as any).toISOString() + : c.object_modified, + object_added_at: c.object_added_at + ? (c.object_added_at as any).toISOString() + : c.object_added_at, + })), + quarantine: this.quarantine?.map(q => ({ + ...q, + object_modified: q.object_modified + ? (q.object_modified as any).toISOString() + : q.object_modified, + })), + composition: this.composition, + composition_resolution: this.composition_resolution, + }; + } + + // Check if an object with the given STIX ID exists in members + public hasMember(objectRef: string): boolean { + return !!this.members.find(m => m.object_ref === objectRef); + } + + // Check if an object with the given STIX ID exists in candidates + public hasCandidate(objectRef: string): boolean { + return !!this.candidates?.find(c => c.object_ref === objectRef); + } + + // Check if an object with the given STIX ID exists in staged + public hasStaged(objectRef: string): boolean { + return !!this.staged?.find(c => c.object_ref === objectRef); + } + + // Get the member entry for the given STIX ID + public findMember(objectRef: string): MemberEntry | undefined { + return this.members.find(m => m.object_ref === objectRef); + } + + // Get the candidate entry for the given STIX ID + public findCandidate(objectRef: string): CandidateEntry | undefined { + return this.candidates?.find(c => c.object_ref === objectRef); + } + + // Get the staged entry for the given STIX ID + public findStaged(objectRef: string): StagedEntry | undefined { + return this.staged?.find(c => c.object_ref === objectRef); + } + + // Add candidate entry + public addCandidate(entry: CandidateEntry): void { + if (!this.candidates) this.candidates = []; + if (!this.hasCandidate(entry.object_ref)) this.candidates.push(entry); + } + + // Remove candidate entry + public removeCandidate(objectRef: string): void { + if (!this.candidates) return; + this.candidates = this.candidates.filter(c => c.object_ref !== objectRef); + } + + // Get a summary of the snapshot object + public toSummary(): any { + return { + id: this.id, + name: this.name, + type: this.type, + version: this.version, + modified: this.modified, + counts: { + members: this.memberCount, + staged: this.stagedCount, + candidates: this.candidateCount, + quarantine: this.quarantineCount, + }, + }; + } +} diff --git a/src/app/classes/release-tracks/tiers.ts b/src/app/classes/release-tracks/tiers.ts new file mode 100644 index 000000000..69a9aa1fe --- /dev/null +++ b/src/app/classes/release-tracks/tiers.ts @@ -0,0 +1,31 @@ +import { WorkflowStatusType } from './enums'; + +export interface MemberEntry { + object_ref: string; + object_modified: Date; +} + +export interface StagedEntry { + object_ref: string; + object_modified: Date; + object_status: WorkflowStatusType; + object_staged_at: Date; + object_staged_by: string; +} + +export interface CandidateEntry { + object_ref: string; + object_modified: Date; + object_status: WorkflowStatusType; + object_added_at: Date; + object_added_by: string; +} + +export interface QuarantineEntry { + object_ref: string; + object_modified: Date; + source_track_id: string; + source_track_name: string; + source_snapshot_version?: string | null; + conflict_reason: string; +} From bfeeabbb01b229a34705bf166de9b7ac4752681b Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Tue, 24 Mar 2026 15:01:03 -0400 Subject: [PATCH 003/190] feat(release-tracks): implement release track API client methods --- .../release-tracks.integration.spec.ts | 144 ++++ .../rest-api/release-tracks.service.ts | 728 ++++++++++++++++++ 2 files changed, 872 insertions(+) create mode 100644 src/app/services/connectors/rest-api/release-tracks.integration.spec.ts create mode 100644 src/app/services/connectors/rest-api/release-tracks.service.ts diff --git a/src/app/services/connectors/rest-api/release-tracks.integration.spec.ts b/src/app/services/connectors/rest-api/release-tracks.integration.spec.ts new file mode 100644 index 000000000..ce5a3518b --- /dev/null +++ b/src/app/services/connectors/rest-api/release-tracks.integration.spec.ts @@ -0,0 +1,144 @@ +import { describe, it, expect, beforeAll } from 'vitest'; +import { environment } from 'src/environments/environment'; + +// Integration tests that exercise the real REST API. These tests will +// silently no-op (skip) if the configured API URL is unreachable. + +const apiUrl = environment.integrations.rest_api.url.replace(/\/+$/, ''); +let serverAvailable = false; +const commonHeaders: Record = { + 'Content-Type': 'application/json', +}; +let discoveredTrackId: string | null = null; + +async function probe(url: string, timeoutMs = 3000): Promise { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), timeoutMs); + try { + const res = await fetch(url, { method: 'GET', signal: controller.signal }); + return res; + } finally { + clearTimeout(id); + } +} + +beforeAll(async () => { + try { + const res = await probe(`${apiUrl}/release-tracks`); + if (res && res.ok) { + serverAvailable = true; + // try to read and pick an id if available + try { + const body = await res.json().catch(() => null); + // server may return paginated object or array + if (Array.isArray(body) && body.length > 0 && body[0].id) { + discoveredTrackId = body[0].id; + } else if ( + body && + body.items && + Array.isArray(body.items) && + body.items.length > 0 && + body.items[0].id + ) { + discoveredTrackId = body.items[0].id; + } + } catch (e) { + console.error(e); + } + } else { + serverAvailable = false; + // leave discoveredTrackId null + } + } catch (e) { + // network error or timeout + serverAvailable = false; + console.error(e); + } +}); + +describe('Release Tracks API integration (real server)', () => { + it('GET /release-tracks (list) should return 200 when server available', async () => { + if (!serverAvailable) { + console.warn( + 'Skipping /release-tracks test because server is unreachable' + ); + return; + } + const res = await fetch(`${apiUrl}/release-tracks`, { + headers: commonHeaders, + }); + expect(res.ok).toBe(true); + const body = await res.json().catch(() => null); + expect(body).toBeTruthy(); + }); + + it('GET /release-tracks/ephemeral/:domain should return a bundle when available', async () => { + if (!serverAvailable) { + console.warn( + 'Skipping ephemeral bundle test because server is unreachable' + ); + return; + } + const domain = 'enterprise'; + const res = await fetch( + `${apiUrl}/release-tracks/ephemeral/${domain}?format=bundle`, + { headers: commonHeaders } + ); + // The API may return different formats; assert no network error and a JSON body when possible + expect(res.ok).toBe(true); + // attempt to parse JSON but do not fail if parsing isn't possible + try { + const json = await res.json(); + expect(json).toBeDefined(); + } catch (e) { + console.error(e); + } + }); + + it('GET /release-tracks/:id -> getLatestSnapshot should return snapshot when tracks exist', async () => { + if (!serverAvailable) { + console.warn( + 'Skipping getLatestSnapshot test because server is unreachable' + ); + return; + } + if (!discoveredTrackId) { + console.warn( + 'Skipping getLatestSnapshot test because no tracks were found in /release-tracks' + ); + return; + } + const res = await fetch( + `${apiUrl}/release-tracks/${encodeURIComponent(discoveredTrackId)}`, + { headers: commonHeaders } + ); + expect(res.ok).toBe(true); + const json = await res.json().catch(() => null); + expect(json).toBeTruthy(); + // If the API returns a shape compatible with ReleaseTrackSnapshot, it usually has id/name/modified + if (json) { + expect(json).toHaveProperty('id'); + } + }); + + it('GET /release-tracks/:id/staged should return 200 (if track exists)', async () => { + if (!serverAvailable) { + console.warn('Skipping listStaged test because server is unreachable'); + return; + } + if (!discoveredTrackId) { + console.warn( + 'Skipping listStaged test because no tracks were found in /release-tracks' + ); + return; + } + const res = await fetch( + `${apiUrl}/release-tracks/${encodeURIComponent(discoveredTrackId)}/staged`, + { headers: commonHeaders } + ); + expect(res.ok).toBe(true); + // body may be array or paginated object + const body = await res.json().catch(() => null); + expect(body).toBeDefined(); + }); +}); diff --git a/src/app/services/connectors/rest-api/release-tracks.service.ts b/src/app/services/connectors/rest-api/release-tracks.service.ts new file mode 100644 index 000000000..96c7dcf3b --- /dev/null +++ b/src/app/services/connectors/rest-api/release-tracks.service.ts @@ -0,0 +1,728 @@ +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { Observable } from 'rxjs'; +import { catchError, share, tap, map } from 'rxjs/operators'; +import { environment } from '../../../../environments/environment'; +import { logger } from '../../../utils/logger'; +import { ApiConnector } from '../api-connector'; +import { + ReleaseTrackSnapshot, + ReleaseTrackType, + WorkflowStatusType, +} from 'src/app/classes/release-tracks'; + +// ----------------------------------------------------------------------------- +// Request Payload Definitions for Release Tracks API Requests +// ----------------------------------------------------------------------------- +export type StixObjectRef = string | { id: string; modified?: string }; + +export interface CreateReleaseTrackPayload { + name: string; + description?: string; + external_references?: any[]; + object_marking_refs?: any[]; + type?: ReleaseTrackType; + composition?: any; + snapshot_schedule?: any; +} + +export interface StixBundlePayload { + type: 'bundle'; + id?: string; + objects: any[]; +} + +export interface UpdateMetadataPayload { + name?: string; + description?: string; + external_references?: any[]; + object_marking_refs?: any[]; +} + +export interface UpdateContentsPayload { + x_mitre_contents: string[]; +} + +export interface BumpPayload { + type?: 'major' | 'minor'; + version?: string; + dry_run?: boolean; +} + +export interface ClonePayload { + name?: string; +} + +export interface ReviewPayload { + from: string; + to: string; + object_refs?: StixObjectRef[]; +} + +export interface UpdateVersionPayload { + old_modified?: string; + new_modified?: string; +} + +export interface ConfigPayload { + candidacy_threshold?: WorkflowStatusType; + auto_promote?: boolean; +} + +// ----------------------------------------------------------------------------- +// Release Tracks API Connector Service +// ----------------------------------------------------------------------------- + +@Injectable({ + providedIn: 'root', +}) +export class ReleaseTracksConnectorService extends ApiConnector { + private get apiUrl(): string { + return environment.integrations.rest_api.url; + } + + constructor( + private http: HttpClient, + snackbar: MatSnackBar + ) { + super(snackbar); + } + + // ----------------------------------------------------------------------------- + // Ephemeral + // ----------------------------------------------------------------------------- + + /** + * GET /api/release-tracks/ephemeral/:domain + * Retrieve an ephemeral STIX bundle for the given domain. + * @param domain Path parameter selecting ATT&CK domain ('enterprise'|'ics'|'mobile') + * @param format Optional query parameter controlling output format ('bundle'|'filesystemstore'|'workbench') + * @returns Observable Server response + */ + public getEphemeralBundle( + domain: string, + format = 'bundle' + ): Observable { + let params = new HttpParams(); + if (format) params = params.set('format', format); + const url = `${this.apiUrl}/release-tracks/ephemeral/${domain}`; + return this.http.get(url, { params }).pipe( + tap(result => logger.log(`retrieved ephemeral ${domain} bundle`, result)), + catchError(this.handleError_continue(null)), + share() + ); + } + + // ----------------------------------------------------------------------------- + // Track management + // ----------------------------------------------------------------------------- + + /** + * GET /api/release-tracks + * List release tracks. + * @param options Query options: type, releases, limit, offset, search + * @returns Observable paginated list + */ + public listReleaseTracks(options?: { + type?: string; + releases?: 'only'; + limit?: number; + offset?: number; + search?: string; + }): Observable { + let params = new HttpParams(); + if (options?.type) params = params.set('type', options.type); + if (options?.releases) params = params.set('releases', options.releases); + if (options?.limit) params = params.set('limit', options.limit.toString()); + if (options?.offset) + params = params.set('offset', options.offset.toString()); + if (options?.search) params = params.set('search', options.search); + const url = `${this.apiUrl}/release-tracks`; + return this.http.get(url, { params }).pipe( + tap(() => logger.log('retrieved release tracks list')), + catchError(this.handleError_continue(null)), + share() + ); + } + + /** + * POST /api/release-tracks/new + * Create a new release track. + * @param body Request payload + * @returns Observable created track + */ + public createReleaseTrack(body: CreateReleaseTrackPayload): Observable { + const url = `${this.apiUrl}/release-tracks/new`; + return this.http.post(url, body).pipe( + tap(result => logger.log('created release track', result)), + catchError(this.handleError_raise()), + share() + ); + } + + /** + * POST /api/release-tracks/new-from-bundle + * Bootstrap a release track from a STIX bundle. + * @param body STIX bundle + * @returns Observable creation result + */ + public createReleaseTrackFromBundle( + body: StixBundlePayload + ): Observable { + const url = `${this.apiUrl}/release-tracks/new-from-bundle`; + return this.http.post(url, body).pipe( + tap(result => logger.log('created release track from bundle', result)), + catchError(this.handleError_raise()), + share() + ); + } + + /** + * POST /api/release-tracks/import + * Import a release track (server may return 501 Not Implemented). + * @param body Import payload (TBD) + * @returns Observable server response + */ + public importReleaseTrack(body: any): Observable { + const url = `${this.apiUrl}/release-tracks/import`; + return this.http.post(url, body).pipe( + tap(result => logger.log('imported release track', result)), + catchError(this.handleError_raise()), + share() + ); + } + + /** + * GET /api/release-tracks/:id + * Get latest snapshot for a track. + * @param id Release track id + * @param options Query options forwarded to endpoint + * @returns Observable snapshot or list + */ + public getLatestSnapshot( + id: string, + options?: { format?: string; [key: string]: any } + ): Observable { + let params = new HttpParams(); + if (options) { + Object.keys(options).forEach(k => { + const v = (options as any)[k]; + if (v !== undefined && v !== null) params = params.set(k, String(v)); + }); + } + const url = `${this.apiUrl}/release-tracks/${id}`; + return this.http.get(url, { params }).pipe( + tap(result => + logger.log(`retrieved latest snapshot for track ${id}`, result) + ), + map(result => (result ? new ReleaseTrackSnapshot(result) : null)), + catchError(this.handleError_continue(null)), + share() + ); + } + + /** + * POST /api/release-tracks/:id/meta + * Update metadata for latest snapshot (creates new snapshot). + * @param id Release track id + * @param body Metadata payload + * @param userAccountId Optional user account id appended to payload + * @returns Observable + */ + public updateMetadataByLatest( + id: string, + body: UpdateMetadataPayload, + userAccountId?: string + ): Observable { + const url = `${this.apiUrl}/release-tracks/${id}/meta`; + const payload = userAccountId ? { ...body, userAccountId } : body; + return this.http.post(url, payload).pipe( + tap(result => logger.log(`updated metadata for track ${id}`, result)), + catchError(this.handleError_raise()), + share() + ); + } + + /** + * POST /api/release-tracks/:id/contents + * Update member contents for latest snapshot. + * @param id Release track id + * @param body Contents payload + * @param userAccountId Optional user + * @returns Observable + */ + public updateContentsByLatest( + id: string, + body: UpdateContentsPayload, + userAccountId?: string + ): Observable { + const url = `${this.apiUrl}/release-tracks/${id}/contents`; + const payload = userAccountId ? { ...body, userAccountId } : body; + return this.http.post(url, payload).pipe( + tap(result => logger.log(`updated contents for track ${id}`, result)), + catchError(this.handleError_raise()), + share() + ); + } + + /** + * GET /api/release-tracks/:id/bump/preview + * Preview the next release for a track. + * @param id Release track id + * @param format Optional output format + * @returns Observable preview payload + */ + public previewBump(id: string, format = 'workbench'): Observable { + let params = new HttpParams(); + if (format) params = params.set('format', format); + const url = `${this.apiUrl}/release-tracks/${id}/bump/preview`; + return this.http.get(url, { params }).pipe( + tap(result => + logger.log(`generated bump preview for track ${id}`, result) + ), + catchError(this.handleError_continue(null)), + share() + ); + } + + /** + * POST /api/release-tracks/:id/bump + * Bump/tag the latest snapshot. + * @param id Release track id + * @param body Bump options (type, version, dry_run) + * @returns Observable + */ + public bumpByLatest(id: string, body: BumpPayload): Observable { + const url = `${this.apiUrl}/release-tracks/${id}/bump`; + return this.http.post(url, body).pipe( + tap(result => logger.log(`bumped version for track ${id}`, result)), + catchError(this.handleError_raise()), + share() + ); + } + + /** + * POST /api/release-tracks/:id/clone + * Clone a new release track from the latest snapshot. + * @param id Source release track id + * @param body Clone options + * @returns Observable + */ + public cloneByLatest(id: string, body?: ClonePayload): Observable { + const url = `${this.apiUrl}/release-tracks/${id}/clone`; + return this.http.post(url, body || {}).pipe( + tap(result => logger.log(`cloned track ${id}`, result)), + catchError(this.handleError_raise()), + share() + ); + } + + /** + * DELETE /api/release-tracks/:id + * Delete a release track. + * @param id Release track id + * @returns Observable + */ + public deleteReleaseTrack(id: string): Observable { + const url = `${this.apiUrl}/release-tracks/${id}`; + return this.http.delete(url).pipe( + tap(() => logger.log(`deleted track ${id}`)), + catchError(this.handleError_raise()), + share() + ); + } + + // ----------------------------------------------------------------------------- + // Snapshot Operations + // ----------------------------------------------------------------------------- + + /** + * GET /api/release-tracks/:id/snapshots/:modified + * Get a specific snapshot by modified timestamp. + * @param id Release track id + * @param modified ISO timestamp identifying the snapshot + * @param options Optional query params + * @returns Observable + */ + public retrieveSnapshotByModified( + id: string, + modified: string, + options?: Record + ): Observable { + let params = new HttpParams(); + if (options) { + Object.keys(options).forEach(k => { + const v = options[k]; + if (v !== undefined && v !== null) params = params.set(k, String(v)); + }); + } + const url = `${this.apiUrl}/release-tracks/${id}/snapshots/${modified}`; + return this.http.get(url, { params }).pipe( + tap(result => + logger.log(`retrieved snapshot ${modified} for track ${id}`, result) + ), + map(result => (result ? new ReleaseTrackSnapshot(result) : null)), + catchError(this.handleError_continue(null)), + share() + ); + } + + /** + * POST /api/release-tracks/:id/snapshots/:modified/meta + * Update metadata for a specific snapshot. + * @param id Release track id + * @param modified Snapshot modified timestamp + * @param body Metadata payload + * @param userAccountId Optional user + * @returns Observable + */ + public updateMetadataByModified( + id: string, + modified: string, + body: UpdateMetadataPayload, + userAccountId?: string + ): Observable { + const url = `${this.apiUrl}/release-tracks/${id}/snapshots/${modified}/meta`; + const payload = userAccountId ? { ...body, userAccountId } : body; + return this.http.post(url, payload).pipe( + tap(result => + logger.log(`updated metadata for snapshot ${modified}`, result) + ), + catchError(this.handleError_raise()), + share() + ); + } + + /** + * POST /api/release-tracks/:id/snapshots/:modified/contents + * Update contents for a specific snapshot. + * @param id Release track id + * @param modified Snapshot modified timestamp + * @param body Contents payload + * @param userAccountId Optional user + * @returns Observable + */ + public updateContentsByModified( + id: string, + modified: string, + body: UpdateContentsPayload, + userAccountId?: string + ): Observable { + const url = `${this.apiUrl}/release-tracks/${id}/snapshots/${modified}/contents`; + const payload = userAccountId ? { ...body, userAccountId } : body; + return this.http.post(url, payload).pipe( + tap(result => + logger.log(`updated contents for snapshot ${modified}`, result) + ), + catchError(this.handleError_raise()), + share() + ); + } + + /** + * POST /api/release-tracks/:id/snapshots/:modified/bump + * Tag/bump a specific snapshot. + * @param id Release track id + * @param modified Snapshot modified timestamp + * @param body Bump options + * @returns Observable + */ + public bumpByModified( + id: string, + modified: string, + body: BumpPayload + ): Observable { + const url = `${this.apiUrl}/release-tracks/${id}/snapshots/${modified}/bump`; + return this.http.post(url, body).pipe( + tap(result => + logger.log(`bumped version for snapshot ${modified}`, result) + ), + catchError(this.handleError_raise()), + share() + ); + } + + /** + * POST /api/release-tracks/:id/snapshots/:modified/clone + * Clone a new release track from a specific snapshot. + * @param id Release track id + * @param modified Snapshot modified timestamp + * @param body Clone options + * @returns Observable + */ + public cloneByModified( + id: string, + modified: string, + body?: ClonePayload + ): Observable { + const url = `${this.apiUrl}/release-tracks/${id}/snapshots/${modified}/clone`; + return this.http.post(url, body || {}).pipe( + tap(result => logger.log(`cloned from snapshot ${modified}`, result)), + catchError(this.handleError_raise()), + share() + ); + } + + /** + * DELETE /api/release-tracks/:id/snapshots/:modified + * Delete a specific snapshot. + * @param id Release track id + * @param modified Snapshot modified timestamp + * @returns Observable + */ + public deleteSnapshotByModified( + id: string, + modified: string + ): Observable { + const url = `${this.apiUrl}/release-tracks/${id}/snapshots/${modified}`; + return this.http.delete(url).pipe( + tap(() => logger.log(`deleted snapshot ${modified} from track ${id}`)), + catchError(this.handleError_raise()), + share() + ); + } + + // ----------------------------------------------------------------------------- + // Candidate Management + // ----------------------------------------------------------------------------- + + /** + * POST /api/release-tracks/:id/candidates + * Add candidate references to latest draft. + * @param id Release track id + * @param object_refs Array of STIX ids or object refs + * @param userAccountId Optional user + * @returns Observable + */ + public addCandidates( + id: string, + object_refs: StixObjectRef[], + userAccountId?: string + ): Observable { + const url = `${this.apiUrl}/release-tracks/${id}/candidates`; + const payload = userAccountId + ? { object_refs, userAccountId } + : { object_refs }; + return this.http.post(url, payload).pipe( + tap(result => logger.log(`added candidates to track ${id}`, result)), + catchError(this.handleError_raise()), + share() + ); + } + + /** + * GET /api/release-tracks/:id/candidates + * List candidates from latest snapshot. + * @param id Release track id + * @param options Query options: status|limit|offset + * @returns Observable + */ + public listCandidates( + id: string, + options?: { status?: string; limit?: number; offset?: number } + ): Observable { + let params = new HttpParams(); + if (options?.status) params = params.set('status', options.status); + if (options?.limit) params = params.set('limit', options.limit.toString()); + if (options?.offset) + params = params.set('offset', options.offset.toString()); + const url = `${this.apiUrl}/release-tracks/${id}/candidates`; + return this.http.get(url, { params }).pipe( + tap(() => logger.log(`listed candidates for track ${id}`)), + catchError(this.handleError_continue(null)), + share() + ); + } + + /** + * DELETE /api/release-tracks/:id/candidates/:objectRef + * Remove a candidate. + * @param id Release track id + * @param objectRef STIX object id + * @returns Observable + */ + public removeCandidate(id: string, objectRef: string): Observable { + const url = `${this.apiUrl}/release-tracks/${id}/candidates/${objectRef}`; + return this.http.delete(url).pipe( + tap(() => logger.log(`removed candidate ${objectRef} from track ${id}`)), + catchError(this.handleError_raise()), + share() + ); + } + + /** + * POST /api/release-tracks/:id/candidates/review + * Bulk transition candidate statuses. + * @param id Release track id + * @param body Review payload + * @param userAccountId Optional user + * @returns Observable + */ + public reviewCandidates( + id: string, + body: ReviewPayload, + userAccountId?: string + ): Observable { + const url = `${this.apiUrl}/release-tracks/${id}/candidates/review`; + const payload = userAccountId ? { ...body, userAccountId } : body; + return this.http.post(url, payload).pipe( + tap(result => logger.log(`reviewed candidates for track ${id}`, result)), + catchError(this.handleError_raise()), + share() + ); + } + + /** + * POST /api/release-tracks/:id/candidates/promote + * Promote candidates to staged. + * @param id Release track id + * @param object_refs Array of STIX ids + * @param userAccountId Optional user + * @returns Observable + */ + public promoteCandidates( + id: string, + object_refs: string[], + userAccountId?: string + ): Observable { + const url = `${this.apiUrl}/release-tracks/${id}/candidates/promote`; + const payload = userAccountId + ? { object_refs, userAccountId } + : { object_refs }; + return this.http.post(url, payload).pipe( + tap(result => logger.log(`promoted candidates for track ${id}`, result)), + catchError(this.handleError_raise()), + share() + ); + } + + /** + * POST /api/release-tracks/:id/candidates/:objectRef/update-version + * Update candidate version pin. + * @param id Release track id + * @param objectRef STIX object id + * @param body Version update payload + * @returns Observable + */ + public updateCandidateVersion( + id: string, + objectRef: string, + body: UpdateVersionPayload + ): Observable { + const url = `${this.apiUrl}/release-tracks/${id}/candidates/${objectRef}/update-version`; + return this.http.post(url, body).pipe( + tap(result => + logger.log(`updated version for candidate ${objectRef}`, result) + ), + catchError(this.handleError_raise()), + share() + ); + } + + // ----------------------------------------------------------------------------- + // Staged Object Management + // ----------------------------------------------------------------------------- + + /** + * GET /api/release-tracks/:id/staged + * List staged objects. + * @param id Release track id + * @returns Observable + */ + public listStaged(id: string): Observable { + const url = `${this.apiUrl}/release-tracks/${id}/staged`; + return this.http.get(url).pipe( + tap(() => logger.log(`listed staged objects for track ${id}`)), + catchError(this.handleError_continue(null)), + share() + ); + } + + /** + * POST /api/release-tracks/:id/staged/demote + * Demote staged objects to candidates. + * @param id Release track id + * @param object_refs Array of STIX ids + * @param userAccountId Optional user + * @returns Observable + */ + public demoteStaged( + id: string, + object_refs: StixObjectRef[], + userAccountId?: string + ): Observable { + const url = `${this.apiUrl}/release-tracks/${id}/staged/demote`; + const payload = userAccountId + ? { object_refs, userAccountId } + : { object_refs }; + return this.http.post(url, payload).pipe( + tap(result => + logger.log(`demoted staged objects for track ${id}`, result) + ), + catchError(this.handleError_raise()), + share() + ); + } + + // ----------------------------------------------------------------------------- + // Release Track Configuration + // ----------------------------------------------------------------------------- + + /** + * GET /api/release-tracks/:id/config + * Get track configuration. + * @param id Release track id + * @returns Observable + */ + public getConfig(id: string): Observable { + const url = `${this.apiUrl}/release-tracks/${id}/config`; + return this.http.get(url).pipe( + tap(() => logger.log(`retrieved config for track ${id}`)), + catchError(this.handleError_continue(null)), + share() + ); + } + + /** + * PUT /api/release-tracks/:id/config + * Update track configuration. + * @param id Release track id + * @param body Configuration payload + * @param userAccountId Optional user + * @returns Observable + */ + public updateConfig( + id: string, + body: ConfigPayload, + userAccountId?: string + ): Observable { + const url = `${this.apiUrl}/release-tracks/${id}/config`; + const payload = userAccountId ? { ...body, userAccountId } : body; + return this.http.put(url, payload).pipe( + tap(result => logger.log(`updated config for track ${id}`, result)), + catchError(this.handleError_raise()), + share() + ); + } + + // ----------------------------------------------------------------------------- + // Version Management + // ----------------------------------------------------------------------------- + + /** + * GET /api/release-tracks/:id/objects/:objectRef/versions + * List object versions in a track. + * @param id Release track id + * @param objectRef STIX object id + * @returns Observable + */ + public listObjectVersions(id: string, objectRef: string): Observable { + const url = `${this.apiUrl}/release-tracks/${id}/objects/${objectRef}/versions`; + return this.http.get(url).pipe( + tap(() => logger.log(`listed versions for object ${objectRef}`)), + catchError(this.handleError_continue(null)), + share() + ); + } +} From 59e0d098183cc601e986dc19a532afd29d7e7ec7 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Mon, 6 Apr 2026 13:30:53 -0400 Subject: [PATCH 004/190] feat: redesign object pages to use tab layout --- src/app/app.module.ts | 2 + .../stix-page-tabs.component.html | 41 ++ .../stix-page-tabs.component.scss | 16 + .../stix-page-tabs.component.ts | 21 + .../analytic-view.component.html | 248 ++++---- .../stix/asset-view/asset-view.component.html | 290 ++++----- .../campaign-view.component.html | 284 ++++----- .../data-component-view.component.html | 246 ++++---- .../data-source-view.component.html | 244 ++++---- .../detection-strategy-view.component.html | 200 +++--- .../stix/group-view/group-view.component.html | 205 ++++--- .../matrix-view/matrix-view.component.html | 272 ++++---- .../mitigation-view.component.html | 198 +++--- .../software-view.component.html | 279 +++++---- .../tactic-view/tactic-view.component.html | 191 +++--- .../technique-view.component.html | 580 +++++++++--------- 16 files changed, 1737 insertions(+), 1580 deletions(-) create mode 100644 src/app/components/stix/stix-page-tabs/stix-page-tabs.component.html create mode 100644 src/app/components/stix/stix-page-tabs/stix-page-tabs.component.scss create mode 100644 src/app/components/stix/stix-page-tabs/stix-page-tabs.component.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 54b5bb72b..26608c6b7 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -227,6 +227,7 @@ import { DictionaryPropertyComponent } from './components/stix/dictionary-proper import { DictionaryEditComponent } from './components/stix/dictionary-property/dictionary-edit/dictionary-edit.component'; import { DictionaryViewComponent } from './components/stix/dictionary-property/dictionary-view/dictionary-view.component'; import { DictionaryDiffComponent } from './components/stix/dictionary-property/dictionary-diff/dictionary-diff.component'; +import { StixPageTabsComponent } from './components/stix/stix-page-tabs/stix-page-tabs.component'; export function initConfig(appConfigService: AppConfigService) { return () => appConfigService.loadAppConfig(); @@ -284,6 +285,7 @@ export function initConfig(appConfigService: AppConfigService) { DatepickerPropertyComponent, IconViewComponent, LandingPageComponent, + StixPageTabsComponent, HelpPageComponent, DashboardPageComponent, DataQualityComponent, diff --git a/src/app/components/stix/stix-page-tabs/stix-page-tabs.component.html b/src/app/components/stix/stix-page-tabs/stix-page-tabs.component.html new file mode 100644 index 000000000..45c5acbe4 --- /dev/null +++ b/src/app/components/stix/stix-page-tabs/stix-page-tabs.component.html @@ -0,0 +1,41 @@ + + + + DETAILS + + +
+ +
+
+
+ + + + + {{ rel.label | uppercase }} + + + + + + + + + + HISTORY + + + + + + + + + NOTES + + + + + +
diff --git a/src/app/components/stix/stix-page-tabs/stix-page-tabs.component.scss b/src/app/components/stix/stix-page-tabs/stix-page-tabs.component.scss new file mode 100644 index 000000000..82af05621 --- /dev/null +++ b/src/app/components/stix/stix-page-tabs/stix-page-tabs.component.scss @@ -0,0 +1,16 @@ +.nav-tab-label { + font-family: Roboto, Arial, sans-serif; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.4px; + display: inline-block; + padding: 0 8px; +} + +.mat-mdc-tab-header .mat-mdc-tab-label .nav-tab-label { + color: inherit; +} + +.details-tpl-wrapper { + margin-top: 20px; +} diff --git a/src/app/components/stix/stix-page-tabs/stix-page-tabs.component.ts b/src/app/components/stix/stix-page-tabs/stix-page-tabs.component.ts new file mode 100644 index 000000000..ade468d8a --- /dev/null +++ b/src/app/components/stix/stix-page-tabs/stix-page-tabs.component.ts @@ -0,0 +1,21 @@ +import { Component, Input, TemplateRef } from '@angular/core'; +import { StixViewConfig } from 'src/app/views/stix/stix-view-page'; + +interface RelationshipTab { + label: string; + template: TemplateRef; +} + +@Component({ + selector: 'app-stix-page-tabs', + templateUrl: './stix-page-tabs.component.html', + styleUrls: ['./stix-page-tabs.component.scss'], + standalone: false, +}) +export class StixPageTabsComponent { + @Input() config!: StixViewConfig; + @Input() detailsTemplate!: TemplateRef; + @Input() relationshipTabs: RelationshipTab[] = []; + @Input() showHistory = true; + @Input() showNotes = true; +} diff --git a/src/app/views/stix/analytic-view/analytic-view.component.html b/src/app/views/stix/analytic-view/analytic-view.component.html index 7ed1ebd25..ed0cf3c8e 100644 --- a/src/app/views/stix/analytic-view/analytic-view.component.html +++ b/src/app/views/stix/analytic-view/analytic-view.component.html @@ -1,4 +1,4 @@ -
+
@@ -8,126 +8,136 @@ object: config.object, }"> + [config]="config">
-
-
- - - -
-
- - -
-
-
- -
- -
-
- - -
-
- @if (config.mode === 'view') { - - -
-
- + + + + + +
+
+
+ + + +
+
+ + +
+
+
+ +
+ +
+
+ + +
+
+ @if (config.mode === 'view') { + + +
+
+ +
+
+ } +
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
- } -
-
- - -
-
-
-
- - -
-
-
-
- - -
-
+
diff --git a/src/app/views/stix/asset-view/asset-view.component.html b/src/app/views/stix/asset-view/asset-view.component.html index ae6fb287e..a2fb9567f 100644 --- a/src/app/views/stix/asset-view/asset-view.component.html +++ b/src/app/views/stix/asset-view/asset-view.component.html @@ -1,4 +1,4 @@ -
+
@@ -7,131 +7,152 @@ mode: config.mode, object: config.object, }"> - +
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
-
- - + + + +
+
+
+ + +
+
+ + +
+
+
+
+ + -
-
-
-
- - + }"> +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+

References

+
+
+
+
+ +
+
+
-
- -
- + + +

Techniques Used

@@ -163,27 +184,6 @@

Techniques Used

}">
-
- -
-
-
-

References

-
-
-
-
- -
-
-
+ +
diff --git a/src/app/views/stix/campaign-view/campaign-view.component.html b/src/app/views/stix/campaign-view/campaign-view.component.html index 0877f923e..24e5bca90 100644 --- a/src/app/views/stix/campaign-view/campaign-view.component.html +++ b/src/app/views/stix/campaign-view/campaign-view.component.html @@ -1,4 +1,4 @@ -
+
@@ -7,131 +7,139 @@ mode: config.mode, object: config.object, }"> - +
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
-
- - + + + + + +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
-
- -
- + + +

Groups

@@ -163,8 +171,9 @@

Groups

}">
+
- +

Techniques Used

@@ -196,8 +205,9 @@

Techniques Used

}">
+
- +

Software Used

@@ -229,27 +239,5 @@

Software Used

}">
-
- -
-
-
-

References

-
-
-
-
- -
-
-
+
diff --git a/src/app/views/stix/data-component-view/data-component-view.component.html b/src/app/views/stix/data-component-view/data-component-view.component.html index 7f05e50a8..22d2dd877 100644 --- a/src/app/views/stix/data-component-view/data-component-view.component.html +++ b/src/app/views/stix/data-component-view/data-component-view.component.html @@ -1,4 +1,4 @@ -
+
+ [config]="{ mode: config.mode, object: config.object }">
-
-
- - -
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
- - @if ( - (config.showRelationships || !config.hasOwnProperty('showRelationships')) && - !editing - ) { -
+ + + + + +
-

Techniques Detected

- + + +
+
+ +
- + - + mode: config.mode, + object: config.object, + field: 'domains', + editType: 'select', + }">
-
- } - -
-
-
-

References

+
+
+ + +
-
-
-
- +
+
+ + +
+
+ + @if ( + (config.showRelationships || !config.hasOwnProperty('showRelationships')) && + !editing + ) { +
+
+
+

Techniques Detected

+ +
+
+
+
+ + +
+
+
+ } + +
+
+
+

References

+
+
+
+
+ +
+
-
+
diff --git a/src/app/views/stix/data-source-view/data-source-view.component.html b/src/app/views/stix/data-source-view/data-source-view.component.html index 84f62cdff..1b1e70520 100644 --- a/src/app/views/stix/data-source-view/data-source-view.component.html +++ b/src/app/views/stix/data-source-view/data-source-view.component.html @@ -1,4 +1,4 @@ -
+
@@ -8,102 +8,131 @@ object: config.object, }"> + [config]="config">
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
-
- - + + + + + +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+
+
+
+

References

+
+
+
+
+ +
+
+
-
- -
- + + +

Data Components

@@ -124,8 +153,9 @@

Data Components

(refresh)="getDataComponents()">
+
- +

Techniques Detected

@@ -145,27 +175,5 @@

Techniques Detected

(refresh)="getDataComponents()">
-
- -
-
-
-

References

-
-
-
-
- -
-
-
+
diff --git a/src/app/views/stix/detection-strategy-view/detection-strategy-view.component.html b/src/app/views/stix/detection-strategy-view/detection-strategy-view.component.html index d54a7ba50..26f361b84 100644 --- a/src/app/views/stix/detection-strategy-view/detection-strategy-view.component.html +++ b/src/app/views/stix/detection-strategy-view/detection-strategy-view.component.html @@ -1,4 +1,4 @@ -
+
@@ -8,85 +8,114 @@ object: config.object, }"> + [config]="config">
-
-
- - -
-
- - -
-
-
-
- - @if (config.mode === 'edit') { - - } @else if (!loading) { - - } @else { -
-
- + + + + + +
+
+
+ + +
+
+ + +
+
+
+
+ + @if (config.mode === 'edit') { + + } @else if (!loading) { + + } @else { +
+
+ +
+ analytics +
+ } +
+
+ + +
+
+ @if ( + detectionStrategy.external_references.list().length > 0 || + previous?.external_references.list().length > 0 + ) { +
+
+

References

+
+
+
+
+
- analytics
}
-
- - -
-
- - @if ( - (config.showRelationships || !config.hasOwnProperty('showRelationships')) && - !editing - ) { - + + +

Techniques

@@ -116,26 +145,5 @@

Techniques

}">
- } - - @if ( - detectionStrategy.external_references.list().length > 0 || - previous?.external_references.list().length > 0 - ) { -
-
-

References

-
-
-
-
- -
-
- } +
diff --git a/src/app/views/stix/group-view/group-view.component.html b/src/app/views/stix/group-view/group-view.component.html index eac275116..4c90b7d86 100644 --- a/src/app/views/stix/group-view/group-view.component.html +++ b/src/app/views/stix/group-view/group-view.component.html @@ -1,4 +1,4 @@ -
+
- + +
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
-
- - + + + + + +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+

References

+
+
+
+
+ +
+
+
-
- -
- + + +

Campaigns

@@ -109,7 +141,9 @@

Campaigns

}">
- +
+ +

Techniques Used

@@ -141,8 +175,9 @@

Techniques Used

}">
+
- +

Software Used

@@ -174,27 +209,5 @@

Software Used

}">
-
- -
-
-
-

References

-
-
-
-
- -
-
-
+
diff --git a/src/app/views/stix/matrix/matrix-view/matrix-view.component.html b/src/app/views/stix/matrix/matrix-view/matrix-view.component.html index c9f9d341c..d830a058a 100644 --- a/src/app/views/stix/matrix/matrix-view/matrix-view.component.html +++ b/src/app/views/stix/matrix/matrix-view/matrix-view.component.html @@ -1,4 +1,4 @@ -
+
- +
-
-
- - -
-
- - -
-
-
-
- - -
-
- -
-
- -

TACTICS

-
-
-
- -
- -
-
- + + + + + +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ +
+
+ +

TACTICS

+
+
+
+ +
+ +
+
+ +
+ tactics +
+
+ +
+
+
+

References

+
+
+
+
+ +
+
- tactics
-
- -
-
-

MATRIX

-
-
- - - -
- - Matrix View - - Side - Flat - - - - + +
+
+

MATRIX

- - +
+ + + +
+ + Matrix View + + Side + Flat + + + + +
+ + +
+ + +
- - + + - - - - - - -
-
-
-

References

-
-
-
-
- -
-
diff --git a/src/app/views/stix/mitigation-view/mitigation-view.component.html b/src/app/views/stix/mitigation-view/mitigation-view.component.html index f7edb22b4..ff76f0766 100644 --- a/src/app/views/stix/mitigation-view/mitigation-view.component.html +++ b/src/app/views/stix/mitigation-view/mitigation-view.component.html @@ -1,4 +1,4 @@ -
+
+ [config]="config"> +
-
-
- - -
-
- - -
-
- -
-
- -
-
- -
-
- -
-
-
-
- - + + + + + +
+
+
+ + +
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ + +
+
+
+
+
+

References

+
+
+
+
+ +
+
+
-
- -
+ + +
-

Techniques Addressed by Mitigation

+

Techniques Addressed

@@ -110,27 +140,5 @@

Techniques Addressed by Mitigation

}">
-
- -
-
-
-

References

-
-
-
-
- -
-
-
+
diff --git a/src/app/views/stix/software-view/software-view.component.html b/src/app/views/stix/software-view/software-view.component.html index b6640b04f..d3621b905 100644 --- a/src/app/views/stix/software-view/software-view.component.html +++ b/src/app/views/stix/software-view/software-view.component.html @@ -1,4 +1,4 @@ -
+
- +
-
-
- - -
-
- - -
-
-
-
- - -
-
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
-
- - + + + + + + +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+
+
+
+

References

+
+
+
+
+ +
+
+
-
- -
- + + +

Techniques Used

@@ -148,8 +178,9 @@

Techniques Used

}">
+
- +

Associated Groups

@@ -181,8 +212,9 @@

Associated Groups

}">
+
- +

Campaigns

@@ -214,27 +246,6 @@

Campaigns

}">
-
- -
-
-
-

References

-
-
-
-
- -
-
-
+ +
diff --git a/src/app/views/stix/tactic-view/tactic-view.component.html b/src/app/views/stix/tactic-view/tactic-view.component.html index 0abba79b6..71c298a02 100644 --- a/src/app/views/stix/tactic-view/tactic-view.component.html +++ b/src/app/views/stix/tactic-view/tactic-view.component.html @@ -1,4 +1,4 @@ -
+
+ [config]="config">
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
-
- - + + + + + +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+
+
+
+

References

+
+
+
+
+ +
+
+
-
+ - - -
+

Techniques

@@ -96,28 +122,5 @@

Techniques

}">
-
- - -
-
-
-

References

-
-
-
-
- -
-
-
+
diff --git a/src/app/views/stix/technique-view/technique-view.component.html b/src/app/views/stix/technique-view/technique-view.component.html index 08b8dd666..32ef1f515 100644 --- a/src/app/views/stix/technique-view/technique-view.component.html +++ b/src/app/views/stix/technique-view/technique-view.component.html @@ -1,4 +1,4 @@ -
+
@@ -11,322 +11,340 @@ ? technique.parentTechnique : null, }"> - +
-
-
- - -
-
- -
-
- - -
-
- -
-
- -
-
- -
-
- -
- - -
- arrow_forward -
-
- -
- + + + +
+
+
+ + + }">
- -
+
+ +
+
+ + +
+
+ +
+
- -
+
+ + +
+
-
- -
-
-
- - -
-
- - -
-
- -
-
- -
-
-
-
- -
-
-
-
- - -
-
- -
-
- -
- - -
- arrow_forward -
-
- -
+ technique.supportsDomainSpecificFields || + previous?.supportsDomainSpecificFields + "> + +
+ arrow_forward +
+
+ +
+ +
+ +
+ +
+ +
+ +
+
+ +
+
+
+
- -
+
+
- -
+
+
+
- -
+
+
+
- -
- +
+
+ + +
+
+ +
+
+ + field: 'tactics', + editType: 'stixList', + disabled: + editing && + (technique.domains.length === 0 || technique.is_subtechnique), + }"> +
+ + +
+ arrow_forward +
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+
+
+
+
+
+

References

+
+
+
+
+ +
- -
-
-
- -
-
- -
- + + +
@@ -382,7 +400,9 @@

- + + +

Campaigns

@@ -414,7 +434,9 @@

Campaigns

}">
- +
+ +

Mitigations

@@ -444,7 +466,9 @@

Mitigations

}">
- +
+ +

Procedure Examples

@@ -474,7 +498,9 @@

Procedure Examples

}">
- +
+ +
@@ -502,7 +528,9 @@

Data Sources

- +
+ +

Detection Strategies

@@ -534,7 +562,9 @@

Detection Strategies

}">
- +
+ +

Assets

@@ -565,27 +595,5 @@

Assets

}">
-
- -
-
-
-

References

-
-
-
-
- -
-
-
+
From 9cddb6cf5c4ea77bef266f09024497857cb07aaf Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Mon, 6 Apr 2026 13:31:08 -0400 Subject: [PATCH 005/190] chore: formatting --- .../analytic-view.component.html | 3 +-- .../stix/asset-view/asset-view.component.html | 19 ++++++++++++------- .../campaign-view.component.html | 2 +- .../data-component-view.component.html | 6 +++--- .../data-source-view.component.html | 5 ++--- .../detection-strategy-view.component.html | 7 ++----- .../stix/group-view/group-view.component.html | 4 ++-- .../matrix-view/matrix-view.component.html | 7 +++---- .../mitigation-view.component.html | 8 ++------ .../software-view.component.html | 3 +-- .../tactic-view/tactic-view.component.html | 7 ++----- .../technique-view.component.html | 19 +++++++++++++------ 12 files changed, 44 insertions(+), 46 deletions(-) diff --git a/src/app/views/stix/analytic-view/analytic-view.component.html b/src/app/views/stix/analytic-view/analytic-view.component.html index ed0cf3c8e..68bc8717e 100644 --- a/src/app/views/stix/analytic-view/analytic-view.component.html +++ b/src/app/views/stix/analytic-view/analytic-view.component.html @@ -7,8 +7,7 @@ mode: config.mode === 'edit' ? 'view' : config.mode, object: config.object, }"> - +
diff --git a/src/app/views/stix/asset-view/asset-view.component.html b/src/app/views/stix/asset-view/asset-view.component.html index a2fb9567f..da60f317a 100644 --- a/src/app/views/stix/asset-view/asset-view.component.html +++ b/src/app/views/stix/asset-view/asset-view.component.html @@ -14,9 +14,7 @@ @@ -105,8 +103,16 @@ label: 'related assets', subtypeFields: [ { name: 'name', editType: 'string', required: true, key: true }, - { name: 'related_asset_sectors', label: 'sectors', editType: 'select' }, - { name: 'description', supportsReferences: true, editType: 'description' } + { + name: 'related_asset_sectors', + label: 'sectors', + editType: 'select', + }, + { + name: 'description', + supportsReferences: true, + editType: 'description', + }, ], tooltip: 'a related asset', }"> @@ -126,7 +132,7 @@ }">
- +
Techniques Used
-
diff --git a/src/app/views/stix/campaign-view/campaign-view.component.html b/src/app/views/stix/campaign-view/campaign-view.component.html index 24e5bca90..d707ebbf9 100644 --- a/src/app/views/stix/campaign-view/campaign-view.component.html +++ b/src/app/views/stix/campaign-view/campaign-view.component.html @@ -17,7 +17,7 @@ [relationshipTabs]="[ { label: 'Techniques', template: techniquesTpl }, { label: 'Groups', template: groupsTpl }, - { label: 'Software', template: softwareTpl } + { label: 'Software', template: softwareTpl }, ]" [showHistory]="true" [showNotes]="true"> diff --git a/src/app/views/stix/data-component-view/data-component-view.component.html b/src/app/views/stix/data-component-view/data-component-view.component.html index 22d2dd877..a1b14122d 100644 --- a/src/app/views/stix/data-component-view/data-component-view.component.html +++ b/src/app/views/stix/data-component-view/data-component-view.component.html @@ -6,8 +6,7 @@ mode: config.mode, object: config.object, }"> - +
@@ -99,7 +98,8 @@
@if ( - (config.showRelationships || !config.hasOwnProperty('showRelationships')) && + (config.showRelationships || + !config.hasOwnProperty('showRelationships')) && !editing ) {
diff --git a/src/app/views/stix/data-source-view/data-source-view.component.html b/src/app/views/stix/data-source-view/data-source-view.component.html index 1b1e70520..18a286403 100644 --- a/src/app/views/stix/data-source-view/data-source-view.component.html +++ b/src/app/views/stix/data-source-view/data-source-view.component.html @@ -7,8 +7,7 @@ mode: config.mode, object: config.object, }"> - +
@@ -17,7 +16,7 @@ [detailsTemplate]="detailsTpl" [relationshipTabs]="[ { label: 'Data Components', template: componentsTpl }, - { label: 'Techniques', template: techniquesTpl } + { label: 'Techniques', template: techniquesTpl }, ]" [showHistory]="true" [showNotes]="true"> diff --git a/src/app/views/stix/detection-strategy-view/detection-strategy-view.component.html b/src/app/views/stix/detection-strategy-view/detection-strategy-view.component.html index 26f361b84..f3fa7fccd 100644 --- a/src/app/views/stix/detection-strategy-view/detection-strategy-view.component.html +++ b/src/app/views/stix/detection-strategy-view/detection-strategy-view.component.html @@ -7,17 +7,14 @@ mode: config.mode, object: config.object, }"> - +
diff --git a/src/app/views/stix/group-view/group-view.component.html b/src/app/views/stix/group-view/group-view.component.html index 4c90b7d86..16b1cb601 100644 --- a/src/app/views/stix/group-view/group-view.component.html +++ b/src/app/views/stix/group-view/group-view.component.html @@ -17,7 +17,7 @@ [relationshipTabs]="[ { label: 'Techniques', template: techniqueTpl }, { label: 'Campaigns', template: campaignTpl }, - { label: 'Software', template: softwareTpl } + { label: 'Software', template: softwareTpl }, ]" [showHistory]="true" [showNotes]="true"> @@ -84,7 +84,7 @@ }">
- +
+ [relationshipTabs]="[{ label: 'Matrix', template: matrixTpl }]" + [showHistory]="true" + [showNotes]="true"> diff --git a/src/app/views/stix/mitigation-view/mitigation-view.component.html b/src/app/views/stix/mitigation-view/mitigation-view.component.html index ff76f0766..48c4b0f75 100644 --- a/src/app/views/stix/mitigation-view/mitigation-view.component.html +++ b/src/app/views/stix/mitigation-view/mitigation-view.component.html @@ -6,18 +6,14 @@ mode: config.mode, object: config.object, }"> - - +
diff --git a/src/app/views/stix/software-view/software-view.component.html b/src/app/views/stix/software-view/software-view.component.html index d3621b905..72b2fa2f2 100644 --- a/src/app/views/stix/software-view/software-view.component.html +++ b/src/app/views/stix/software-view/software-view.component.html @@ -17,7 +17,7 @@ [relationshipTabs]="[ { label: 'Techniques', template: techniquesTpl }, { label: 'Groups', template: groupsTpl }, - { label: 'Campaigns', template: campaignsTpl } + { label: 'Campaigns', template: campaignsTpl }, ]" [showHistory]="true" [showNotes]="true"> @@ -247,5 +247,4 @@

Campaigns

-
diff --git a/src/app/views/stix/tactic-view/tactic-view.component.html b/src/app/views/stix/tactic-view/tactic-view.component.html index 71c298a02..ce99010fd 100644 --- a/src/app/views/stix/tactic-view/tactic-view.component.html +++ b/src/app/views/stix/tactic-view/tactic-view.component.html @@ -6,17 +6,14 @@ mode: config.mode, object: config.object, }"> - +
diff --git a/src/app/views/stix/technique-view/technique-view.component.html b/src/app/views/stix/technique-view/technique-view.component.html index 32ef1f515..f29f96dca 100644 --- a/src/app/views/stix/technique-view/technique-view.component.html +++ b/src/app/views/stix/technique-view/technique-view.component.html @@ -24,7 +24,7 @@ { label: 'Mitigations', template: mitigationsTpl }, { label: 'Detection Strategies', template: detectsTpl }, { label: 'Campaigns', template: campaignsTpl }, - { label: 'Assets', template: assetsTpl } + { label: 'Assets', template: assetsTpl }, ]" [showHistory]="true" [showNotes]="true"> @@ -77,7 +77,7 @@ }">
- +
@@ -102,7 +102,9 @@
Date: Mon, 6 Apr 2026 14:51:24 -0400 Subject: [PATCH 006/190] fix: remove placeholder values from aliases --- src/app/classes/stix/campaign.ts | 2 +- src/app/classes/stix/group.ts | 2 +- src/app/classes/stix/software.ts | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/app/classes/stix/campaign.ts b/src/app/classes/stix/campaign.ts index 00a107e7c..9a1e7203a 100644 --- a/src/app/classes/stix/campaign.ts +++ b/src/app/classes/stix/campaign.ts @@ -11,7 +11,7 @@ export class Campaign extends StixObject { public last_seen: Date; public first_seen_citation = ''; public last_seen_citation = ''; - public aliases: string[] = ['placeholder']; // initialize field with placeholder in first index for campaign name + public aliases: string[] = []; public contributors: string[] = []; public readonly supportsAttackID = true; diff --git a/src/app/classes/stix/group.ts b/src/app/classes/stix/group.ts index 947c56aee..a23539f6f 100644 --- a/src/app/classes/stix/group.ts +++ b/src/app/classes/stix/group.ts @@ -7,7 +7,7 @@ import { WorkflowState } from 'src/app/utils/types'; export class Group extends StixObject { public name = ''; - public aliases: string[] = ['placeholder']; // initialize field with placeholder in first index for group name + public aliases: string[] = []; public contributors: string[] = []; public readonly supportsAttackID = true; diff --git a/src/app/classes/stix/software.ts b/src/app/classes/stix/software.ts index ba331fdd9..49be00804 100644 --- a/src/app/classes/stix/software.ts +++ b/src/app/classes/stix/software.ts @@ -9,7 +9,7 @@ type type_software = 'malware' | 'tool'; export class Software extends StixObject { public name = ''; public description: string; - public aliases: string[] = ['placeholder']; // initialize field with placeholder in first index for software name + public aliases: string[] = []; public platforms: string[] = []; public type: string; public contributors: string[] = []; @@ -153,8 +153,6 @@ export class Software extends StixObject { * @returns {Observable} of the post */ public save(restAPIService: RestApiConnectorService): Observable { - // update first index of aliases field to software name - this.aliases[0] = this.name; const postObservable = restAPIService.postSoftware(this); const subscription = postObservable.subscribe({ next: result => { From 4f0e587c15ab72df6988c6f91a9027d257086e95 Mon Sep 17 00:00:00 2001 From: adpare Date: Mon, 6 Apr 2026 14:52:22 -0400 Subject: [PATCH 007/190] fix: remove required tag from certain fields in campaign objects --- src/app/views/stix/campaign-view/campaign-view.component.html | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/app/views/stix/campaign-view/campaign-view.component.html b/src/app/views/stix/campaign-view/campaign-view.component.html index 0877f923e..f61041ff7 100644 --- a/src/app/views/stix/campaign-view/campaign-view.component.html +++ b/src/app/views/stix/campaign-view/campaign-view.component.html @@ -43,7 +43,6 @@ object: config.object, field: 'first_seen', label: 'first seen', - required: true, }">
@@ -55,7 +54,6 @@ object: config.object, field: 'last_seen', label: 'last seen', - required: true, }">
@@ -69,7 +67,6 @@ field: 'first_seen_citation', referencesField: 'external_references', label: 'first seen citation', - required: true, }">
@@ -81,7 +78,6 @@ field: 'last_seen_citation', referencesField: 'external_references', label: 'last seen citation', - required: true, }">
From 796f331827ac10fc302ea5e393abe9c4d7976cbb Mon Sep 17 00:00:00 2001 From: adpare Date: Mon, 6 Apr 2026 15:23:38 -0400 Subject: [PATCH 008/190] fix: remove first alias handling from save operations for campaigns and groups --- src/app/classes/stix/campaign.ts | 2 -- src/app/classes/stix/group.ts | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/app/classes/stix/campaign.ts b/src/app/classes/stix/campaign.ts index 9a1e7203a..c6483bdb8 100644 --- a/src/app/classes/stix/campaign.ts +++ b/src/app/classes/stix/campaign.ts @@ -172,8 +172,6 @@ export class Campaign extends StixObject { * @returns {Observable} of the post */ public save(restAPIService: RestApiConnectorService): Observable { - // update first index of aliases field to campaign name - this.aliases[0] = this.name; const postObservable = restAPIService.postCampaign(this); const subscription = postObservable.subscribe({ next: result => { diff --git a/src/app/classes/stix/group.ts b/src/app/classes/stix/group.ts index a23539f6f..761495329 100644 --- a/src/app/classes/stix/group.ts +++ b/src/app/classes/stix/group.ts @@ -111,8 +111,6 @@ export class Group extends StixObject { * @returns {Observable} of the post */ public save(restAPIService: RestApiConnectorService): Observable { - // update first index of aliases field to group name - this.aliases[0] = this.name; const postObservable = restAPIService.postGroup(this); const subscription = postObservable.subscribe({ next: result => { From 13468b75b1b3b24b150765d7bb9355bd8644b2e5 Mon Sep 17 00:00:00 2001 From: adpare Date: Tue, 7 Apr 2026 00:17:44 -0400 Subject: [PATCH 009/190] fix: update how aliases are displayed for software, group and campaigns --- .../alias-property/alias-edit/alias-edit.component.html | 2 +- .../alias-property/alias-edit/alias-edit.component.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/app/components/stix/alias-property/alias-edit/alias-edit.component.html b/src/app/components/stix/alias-property/alias-edit/alias-edit.component.html index ef165eb27..b511e4092 100644 --- a/src/app/components/stix/alias-property/alias-edit/alias-edit.component.html +++ b/src/app/components/stix/alias-property/alias-edit/alias-edit.component.html @@ -2,7 +2,7 @@
diff --git a/src/app/components/stix/alias-property/alias-edit/alias-edit.component.ts b/src/app/components/stix/alias-property/alias-edit/alias-edit.component.ts index 9221c385a..90a573127 100644 --- a/src/app/components/stix/alias-property/alias-edit/alias-edit.component.ts +++ b/src/app/components/stix/alias-property/alias-edit/alias-edit.component.ts @@ -56,4 +56,12 @@ export class AliasEditComponent implements OnInit { }); obj.external_references.deserialize(references); } + +// When displaying aliases for software, groups and campaigns, filter out the object's name from the aliases list + public get visibleAliases(): string[] { + const object = this.config.object as StixObject; + const aliases = object?.[this.config.field] ?? []; + const name = 'name' in object ? object.name : undefined; + return aliases.filter(alias => alias !== name); + } } From 924d9148e36a08fc9234ba666680ab1f6fabfd72 Mon Sep 17 00:00:00 2001 From: adpare Date: Tue, 7 Apr 2026 00:18:28 -0400 Subject: [PATCH 010/190] chore: update identation --- .../stix/alias-property/alias-edit/alias-edit.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/components/stix/alias-property/alias-edit/alias-edit.component.ts b/src/app/components/stix/alias-property/alias-edit/alias-edit.component.ts index 90a573127..5f2e68903 100644 --- a/src/app/components/stix/alias-property/alias-edit/alias-edit.component.ts +++ b/src/app/components/stix/alias-property/alias-edit/alias-edit.component.ts @@ -56,8 +56,8 @@ export class AliasEditComponent implements OnInit { }); obj.external_references.deserialize(references); } - -// When displaying aliases for software, groups and campaigns, filter out the object's name from the aliases list + + // When displaying aliases for software, groups and campaigns, filter out the object's name from the aliases list public get visibleAliases(): string[] { const object = this.config.object as StixObject; const aliases = object?.[this.config.field] ?? []; From d0f493c447678b3435b612bc8ab6777aa875e8ed Mon Sep 17 00:00:00 2001 From: Sean Sica <23294618+seansica@users.noreply.github.com> Date: Wed, 8 Apr 2026 15:26:15 -0400 Subject: [PATCH 011/190] fix: call setOrganizationIdentity when identity is saved instead of postIdentity setOrganizationIdentity will chain postIdentity with setOrganizationIdentity and should have been called all along. --- .../org-settings-page/org-settings-page.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/views/dashboard-page/org-settings-page/org-settings-page.component.ts b/src/app/views/dashboard-page/org-settings-page/org-settings-page.component.ts index b491d081e..7568be3e2 100644 --- a/src/app/views/dashboard-page/org-settings-page/org-settings-page.component.ts +++ b/src/app/views/dashboard-page/org-settings-page/org-settings-page.component.ts @@ -61,7 +61,7 @@ export class OrgSettingsPageComponent implements OnInit { saveIdentity(): void { const subscription = this.restAPIConnector - .postIdentity(this.organizationIdentity) + .setOrganizationIdentity(this.organizationIdentity) .subscribe({ next: identity => (this.organizationIdentity = identity), complete: () => subscription.unsubscribe(), From 49bb8bae381090acb6dd96c31bb07a0637ba1d07 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Thu, 16 Apr 2026 10:09:17 -0400 Subject: [PATCH 012/190] fix: show attack id as auto-generated on save for new objects --- .../stix/attackid-property/attackid-property.component.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/components/stix/attackid-property/attackid-property.component.html b/src/app/components/stix/attackid-property/attackid-property.component.html index 2e6ed69de..ddc362482 100644 --- a/src/app/components/stix/attackid-property/attackid-property.component.html +++ b/src/app/components/stix/attackid-property/attackid-property.component.html @@ -27,6 +27,10 @@ aria-label="copy linkById"> content_copy + } @else { + + Auto-generated on save. + }
ATT&CK ID From 23e86f33911ed97d02b4779df1d1ce1400e4f8e6 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Thu, 16 Apr 2026 10:09:41 -0400 Subject: [PATCH 013/190] chore: formatting --- .../stix/attackid-property/attackid-property.component.html | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/app/components/stix/attackid-property/attackid-property.component.html b/src/app/components/stix/attackid-property/attackid-property.component.html index ddc362482..ede1e805c 100644 --- a/src/app/components/stix/attackid-property/attackid-property.component.html +++ b/src/app/components/stix/attackid-property/attackid-property.component.html @@ -28,9 +28,7 @@ content_copy } @else { - - Auto-generated on save. - + Auto-generated on save. }
ATT&CK ID From a72bbb681f46be60e0ebfa2f65954fb050828e53 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Thu, 16 Apr 2026 15:27:42 -0400 Subject: [PATCH 014/190] feat(release-tracks): added release management dashboard page --- src/app/app-routing.module.ts | 10 ++ src/app/app.module.ts | 7 + .../new-track-dialog.component.html | 86 +++++++++ .../new-track-dialog.component.scss | 62 +++++++ .../new-track-dialog.component.ts | 117 ++++++++++++ .../rest-api/release-tracks.service.ts | 3 +- .../dashboard-page.component.html | 6 + .../release-management.component.html | 165 +++++++++++++++++ .../release-management.component.scss | 140 +++++++++++++++ .../release-management.component.ts | 167 ++++++++++++++++++ src/style/typography.scss | 3 + 11 files changed, 765 insertions(+), 1 deletion(-) create mode 100644 src/app/components/new-track-dialog/new-track-dialog.component.html create mode 100644 src/app/components/new-track-dialog/new-track-dialog.component.scss create mode 100644 src/app/components/new-track-dialog/new-track-dialog.component.ts create mode 100644 src/app/views/dashboard-page/release-management/release-management.component.html create mode 100644 src/app/views/dashboard-page/release-management/release-management.component.scss create mode 100644 src/app/views/dashboard-page/release-management/release-management.component.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index ea3e93aa0..f3a20208d 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -12,6 +12,7 @@ import { AuthorizationGuard } from './services/helpers/authorization.guard'; import { Role } from './classes/authn/role'; import { TeamsListPageComponent } from './views/dashboard-page/teams/teams-list-page/teams-list-page.component'; import { TeamsViewPageComponent } from './views/dashboard-page/teams/teams-view-page/teams-view-page.component'; +import { ReleaseManagementComponent } from './views/dashboard-page/release-management/release-management.component'; const editRoles = [Role.EDITOR, Role.TEAM_LEAD, Role.ADMIN]; @@ -54,6 +55,15 @@ const routes: Routes = [ }, component: DashboardPageComponent, }, + { + path: 'release-management', + data: { + breadcrumb: 'release management', + title: 'Release Management', + roles: [Role.ADMIN, Role.TEAM_LEAD], + }, + component: ReleaseManagementComponent, + }, { path: 'teams', data: { diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 26608c6b7..dd837ba29 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -45,6 +45,7 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatRadioModule } from '@angular/material/radio'; import { MatSelectModule } from '@angular/material/select'; import { MatSidenavModule } from '@angular/material/sidenav'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatSortModule } from '@angular/material/sort'; import { MatStepperModule } from '@angular/material/stepper'; @@ -228,6 +229,8 @@ import { DictionaryEditComponent } from './components/stix/dictionary-property/d import { DictionaryViewComponent } from './components/stix/dictionary-property/dictionary-view/dictionary-view.component'; import { DictionaryDiffComponent } from './components/stix/dictionary-property/dictionary-diff/dictionary-diff.component'; import { StixPageTabsComponent } from './components/stix/stix-page-tabs/stix-page-tabs.component'; +import { ReleaseManagementComponent } from './views/dashboard-page/release-management/release-management.component'; +import { NewTrackDialogComponent } from './components/new-track-dialog/new-track-dialog.component'; export function initConfig(appConfigService: AppConfigService) { return () => appConfigService.loadAppConfig(); @@ -374,11 +377,14 @@ export function initConfig(appConfigService: AppConfigService) { DictionaryEditComponent, DictionaryViewComponent, DictionaryDiffComponent, + ReleaseManagementComponent, + NewTrackDialogComponent, ], exports: [ MatToolbarModule, MatButtonModule, MatSidenavModule, + MatSlideToggleModule, MatIconModule, MatTableModule, MatSortModule, @@ -424,6 +430,7 @@ export function initConfig(appConfigService: AppConfigService) { MatToolbarModule, MatButtonModule, MatSidenavModule, + MatSlideToggleModule, MatIconModule, MatTableModule, MatSortModule, diff --git a/src/app/components/new-track-dialog/new-track-dialog.component.html b/src/app/components/new-track-dialog/new-track-dialog.component.html new file mode 100644 index 000000000..b1a786e1c --- /dev/null +++ b/src/app/components/new-track-dialog/new-track-dialog.component.html @@ -0,0 +1,86 @@ +
+

Create New Release Track

+ +
+ + Track Name + + + + + Description + + + + + +
+
+
Auto-promote Candidates
+
+ Automatically promote Candidates to "Staged" when they meet the threshold. +
+
+ + + Auto-promote + + +
+
+ + Candidacy Threshold + + {{ + opt + }} + + +
+ + + + + Member Sync Strategy + + {{ + opt + }} + + + Determines if new revisions to existing member objects are automatically added as candidates. + + + + + Supplant Behavior + + {{ + opt + }} + + + Dictates how to handle a new version of an object that is already staged. + + +
+ + + + + +
diff --git a/src/app/components/new-track-dialog/new-track-dialog.component.scss b/src/app/components/new-track-dialog/new-track-dialog.component.scss new file mode 100644 index 000000000..0a2514091 --- /dev/null +++ b/src/app/components/new-track-dialog/new-track-dialog.component.scss @@ -0,0 +1,62 @@ +@use '../../../style/globals'; +@use '../../../style/colors'; + +.new-track-dialog { + margin: 0 24px; + height: 100%; + display: flex; + flex-direction: column; + + .divider { + margin: 12px 0; + } + + form { + display: flex; + flex-direction: column; + gap: 20px; + } + + .section-block { + display: flex; + justify-content: space-between; + align-items: center; + } + + .section-title { + font-weight: 600; + color: rgba(0, 0, 0, 0.85); + } + .section-sub { + font-size: 0.9em; + color: rgba(0, 0, 0, 0.6); + } + + .mat-mdc-form-field { + width: 100%; + } + + .no-hint .mat-mdc-form-field-subscript-wrapper { + display: none; // remove bottom padding used for hint + } + + mat-hint { + color: rgba(0, 0, 0, 0.65); + } + + // actions pinned to bottom of dialog + mat-dialog-actions { + display: flex; + justify-content: flex-end; + margin: 16px 0px; + padding: 0px; + + button + button { + margin-left: 12px; + } + + .success { + color: colors.color(success); + } + } +} diff --git a/src/app/components/new-track-dialog/new-track-dialog.component.ts b/src/app/components/new-track-dialog/new-track-dialog.component.ts new file mode 100644 index 000000000..7a6030852 --- /dev/null +++ b/src/app/components/new-track-dialog/new-track-dialog.component.ts @@ -0,0 +1,117 @@ +import { Component, OnInit, ViewEncapsulation, Inject } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { + WorkflowStatus, + MemberSyncStrategy, + MemberSyncBehavior, + ReleaseTrack, +} from 'src/app/classes/release-tracks/enums'; +import { ReleaseTracksConnectorService } from 'src/app/services/connectors/rest-api/release-tracks.service'; + +@Component({ + standalone: false, + selector: 'app-new-track-dialog', + templateUrl: './new-track-dialog.component.html', + styleUrls: ['./new-track-dialog.component.scss'], + encapsulation: ViewEncapsulation.None, +}) +export class NewTrackDialogComponent implements OnInit { + public form: FormGroup; + public loading = false; + + public WorkflowStatus = WorkflowStatus; + public MemberSyncStrategy = MemberSyncStrategy; + public MemberSyncBehavior = MemberSyncBehavior; + + public candidacyOptions = Object.values(WorkflowStatus); + public memberSyncOptions = Object.values(MemberSyncStrategy); + public supplantOptions = Object.values(MemberSyncBehavior); + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any, + private fb: FormBuilder, + private connector: ReleaseTracksConnectorService + ) { + this.form = this.fb.group({ + name: ['', [Validators.required]], + description: [''], + autoPromote: [false], + candidacyThreshold: [{ value: WorkflowStatus.Reviewed, disabled: true }], + memberSync: [MemberSyncStrategy.TrackLatest], + supplantBehavior: [MemberSyncBehavior.Replace], + }); + } + + ngOnInit(): void { + if (this.data) { + const init = this.data || {}; + if (init.name) this.form.get('name')?.setValue(init.name); + if (init.description) + this.form.get('description')?.setValue(init.description); + if (typeof init.autoPromote !== 'undefined') + this.form.get('autoPromote')?.setValue(!!init.autoPromote); + if (init.candidacyThreshold) + this.form.get('candidacyThreshold')?.setValue(init.candidacyThreshold); + if (init.memberSync) + this.form.get('memberSync')?.setValue(init.memberSync); + if (init.supplantBehavior) + this.form.get('supplantBehavior')?.setValue(init.supplantBehavior); + } + + const autoCtrl = this.form.get('autoPromote'); + const candidacyCtrl = this.form.get('candidacyThreshold'); + if (autoCtrl && candidacyCtrl) { + // Initialize based on current value + if (autoCtrl.value) candidacyCtrl.enable({ emitEvent: false }); + else candidacyCtrl.disable({ emitEvent: false }); + + // Subscribe to changes to enable/disable control + autoCtrl.valueChanges.subscribe(value => { + if (value) candidacyCtrl.enable(); + else candidacyCtrl.disable(); + }); + } + } + + public closeDialog(): void { + this.dialogRef.close(); + } + + public isFormValid(): boolean { + return this.form.valid && !!this.form.get('name')?.value?.trim(); + } + + public handleCreate(): void { + if (!this.isFormValid() || this.loading) return; + + const payload: any = { + name: this.form.get('name')?.value, + description: this.form.get('description')?.value, + config: { + auto_promote: !!this.form.get('autoPromote')?.value, + candidacy_threshold: this.form.get('candidacyThreshold')?.value, + member_sync: { + strategy: this.form.get('memberSync')?.value, + supplant: this.form.get('supplantBehavior')?.value, + }, + }, + type: ReleaseTrack.Standard, + }; + + this.loading = true; + const subscription = this.connector.createReleaseTrack(payload).subscribe({ + next: result => { + this.dialogRef.close(result); + }, + error: () => { + // leave dialog open and stop loading + this.loading = false; + }, + complete: () => { + if (subscription) subscription.unsubscribe(); + }, + }); + } +} diff --git a/src/app/services/connectors/rest-api/release-tracks.service.ts b/src/app/services/connectors/rest-api/release-tracks.service.ts index 96c7dcf3b..3ede228cf 100644 --- a/src/app/services/connectors/rest-api/release-tracks.service.ts +++ b/src/app/services/connectors/rest-api/release-tracks.service.ts @@ -11,6 +11,7 @@ import { ReleaseTrackType, WorkflowStatusType, } from 'src/app/classes/release-tracks'; +import { Paginated } from './rest-api-connector.service'; // ----------------------------------------------------------------------------- // Request Payload Definitions for Release Tracks API Requests @@ -130,7 +131,7 @@ export class ReleaseTracksConnectorService extends ApiConnector { limit?: number; offset?: number; search?: string; - }): Observable { + }): Observable> { let params = new HttpParams(); if (options?.type) params = params.set('type', options.type); if (options?.releases) params = params.set('releases', options.releases); diff --git a/src/app/views/dashboard-page/dashboard-page.component.html b/src/app/views/dashboard-page/dashboard-page.component.html index aa9236846..8a37e0013 100644 --- a/src/app/views/dashboard-page/dashboard-page.component.html +++ b/src/app/views/dashboard-page/dashboard-page.component.html @@ -7,6 +7,12 @@

Dashboard

+ +
+
+ +
+
+ +
+

+ Release Tracks ({{ standardTracks.length }}) +

+
+
+
+ + {{ track.name }} + + Standard +
+ + @if (track.description) { + + {{ track.description }} + + } + +
+
+
CANDIDATES
+
+ {{ track.stats?.candidates || track.snapshot_count || 0 }} +
+
+
+
STAGED
+
{{ track.stats?.staged || 0 }}
+
+
+
MEMBERS
+
+ {{ track.stats?.members || track.tagged_release_count || 0 }} +
+
+
+ + + + +
+
+
+ + +
+

+ Virtual Tracks ({{ virtualTracks.length }}) +

+
+
+
+ + {{ track.name }} + + Virtual +
+ + @if (track.description) { + + {{ track.description }} + + } + +
+
+
COMPONENTS
+
+ {{ track.composition?.component_tracks?.length || 0 }} +
+
+
+
QUARANTINED
+
+ {{ track.stats?.quarantined || 0 }} +
+
+
+
MEMBERS
+
{{ track.stats?.members || 0 }}
+
+
+ + + + +
+
+
+
+
+
diff --git a/src/app/views/dashboard-page/release-management/release-management.component.scss b/src/app/views/dashboard-page/release-management/release-management.component.scss new file mode 100644 index 000000000..f44325ba6 --- /dev/null +++ b/src/app/views/dashboard-page/release-management/release-management.component.scss @@ -0,0 +1,140 @@ +@use '../../../../style/globals'; +@use '../../../../style/colors'; + +.release-management { + .controls { + margin-bottom: 16px; + } + + .sections-row { + /* Force the sections to stack vertically so Standard is always above Virtual */ + display: block; + width: 100%; + } + + .cards-container { + display: flex; + flex-direction: column; + gap: 12px; + padding: 10px; + + .cards-grid { + /* responsive grid for cards inside each column */ + display: grid; + grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); + gap: 24px; + } + + /* lightweight wrapper for each card to ensure consistent surface styles + Added explicit fallback colors (do not rely solely on color() accessors) + to guard against build-time issues or runtime theming differences. */ + .cards-grid > .list-card, + .cards-grid > div.list-card { + border-radius: 8px; + padding: 1.5rem; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08); + display: flex; + flex-direction: column; + justify-content: space-between; + border: 1px solid rgba(0, 0, 0, 0.06); + min-height: 10rem; + box-sizing: border-box; + /* Fallback surface colors so cards render even without theme context */ + background: #ffffff; + color: #111111; + + /* Theme-aware overrides (preferred) */ + .light & { + background: colors.color(light); + color: colors.on-color(light); + } + .dark & { + background: colors.color(dark); + color: colors.on-color(dark); + } + } + + .card-standard, + .card-virtual { + /* card surface */ + border-radius: 8px; + padding: 24px; + min-height: 12rem; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.35); + display: flex; + flex-direction: column; + justify-content: space-between; + + .light & { + background: colors.color(light); + } + + .dark & { + background: colors.color(dark); + } + + .card-top { + display: flex; + justify-content: space-between; + align-items: center; + margin: 8px 0; + } + + .card-desc { + color: colors.on-color-deemphasis(dark); + margin: 6px 0; + } + + .card-stats { + display: flex; + gap: 12px; + background: rgba(0, 0, 0, 0.06); + padding: 12px; + border-radius: 8px; + margin-top: 12px; + } + + .stat { + flex: 1; + text-align: center; + } + + .stat-label { + font-size: 11px; + color: colors.on-color-deemphasis(dark); + text-transform: uppercase; + letter-spacing: 0.06em; + margin-bottom: 6px; + } + + .stat-value { + font-weight: 700; + font-size: 1.25rem; + } + + .card-sep { + margin: 12px 0; + } + + .card-footer { + display: flex; + justify-content: space-between; + color: colors.on-color-deemphasis(dark); + font-size: 13px; + } + } + + .tiny-icon { + font-size: 14px !important; + width: 14px !important; + height: 14px !important; + line-height: 14px !important; + margin-right: 6px; + } + + .version { + display: flex; + align-items: center; + } + } +} diff --git a/src/app/views/dashboard-page/release-management/release-management.component.ts b/src/app/views/dashboard-page/release-management/release-management.component.ts new file mode 100644 index 000000000..8b52dfe67 --- /dev/null +++ b/src/app/views/dashboard-page/release-management/release-management.component.ts @@ -0,0 +1,167 @@ +import { + Component, + OnInit, + OnDestroy, + Output, + EventEmitter, + ViewEncapsulation, +} from '@angular/core'; +import { Subscription } from 'rxjs'; +import { ReleaseTracksConnectorService } from 'src/app/services/connectors/rest-api/release-tracks.service'; +import { ReleaseTrack } from 'src/app/classes/release-tracks'; +import { MatDialog } from '@angular/material/dialog'; +import { NewTrackDialogComponent } from 'src/app/components/new-track-dialog/new-track-dialog.component'; + +@Component({ + selector: 'app-release-tracks-list', + templateUrl: './release-management.component.html', + styleUrls: ['./release-management.component.scss'], + encapsulation: ViewEncapsulation.None, + standalone: false, +}) +export class ReleaseManagementComponent implements OnInit, OnDestroy { + @Output() viewTrack = new EventEmitter(); + + public filter: 'all' | 'Standard' | 'Virtual' = 'all'; + public allTracks: any[] = []; + + private subscription: Subscription | null = null; + + public get standardTracks(): any[] { + return this.allTracks.filter( + t => t.type === ReleaseTrack.Standard || t.type === 'standard' + ); + } + + public get virtualTracks(): any[] { + return this.allTracks.filter( + t => t.type === ReleaseTrack.Virtual || t.type === 'virtual' + ); + } + + constructor( + private connector: ReleaseTracksConnectorService, + private dialog: MatDialog + ) {} + + ngOnInit(): void { + this.loadTracks(); + } + + ngOnDestroy(): void { + this.subscription?.unsubscribe(); + } + + private loadTracks(): void { + this.subscription = this.connector.listReleaseTracks().subscribe(result => { + if (!result || !result.data) { + this.allTracks = []; + return; + } + this.allTracks = this.tracksWithComputedData(result.data); + }); + } + + public setFilter(value: 'all' | 'Standard' | 'Virtual'): void { + this.filter = value; + } + + public onViewTrack(id: string): void { + this.viewTrack.emit(id); + } + + private tracksWithComputedData(data: any[]): any[] { + return data?.map((track: any) => { + // If snapshots are available, compute stats from them. Otherwise use + // server-provided summary fields like latest_snapshot_modified. + const snapshots: any[] = Array.isArray(track.snapshots) + ? track.snapshots + : []; + + let latestVersion: any = 'No tagged releases'; + let latestModified: any = + track.latest_snapshot_modified || + track.created_at || + track.updated_at || + null; + let stats: any = { + candidates: 0, + staged: 0, + members: 0, + quarantined: 0, + }; + + if (snapshots.length) { + const sortedByModified = [...snapshots].sort((a, b) => { + const ma = a?.modified || ''; + const mb = b?.modified || ''; + return String(mb).localeCompare(String(ma)); + }); + + const latestSnapshot = sortedByModified[0] || null; + const taggedSnapshots = sortedByModified.filter( + (s: any) => s && s.version + ); + const latestTaggedSnapshot = + taggedSnapshots.length > 0 ? taggedSnapshots[0] : null; + + latestVersion = latestTaggedSnapshot + ? latestTaggedSnapshot.version + : 'No tagged releases'; + latestModified = + latestSnapshot?.modified || latestSnapshot?.created || latestModified; + + stats = { + candidates: + latestSnapshot?.candidates?.length || + latestSnapshot?.contents?.candidates?.length || + 0, + staged: + latestSnapshot?.staged?.length || + latestSnapshot?.contents?.staged?.length || + 0, + members: + latestTaggedSnapshot?.members?.length || + latestTaggedSnapshot?.contents?.members?.length || + 0, + quarantined: + latestSnapshot?.quarantine?.length || + latestSnapshot?.contents?.quarantine?.length || + 0, + }; + } else { + latestVersion = track.latest_tagged_version || 'No tagged releases'; + latestModified = track.latest_snapshot_modified || latestModified; + stats = { + candidates: track.snapshot_count || 0, + staged: 0, + members: track.tagged_release_count || 0, + quarantined: 0, + }; + } + + return { + ...track, + latestVersion, + latestModified, + stats, + }; + }); + } + + public openNewTrackDialog(): void { + const dialogRef = this.dialog.open(NewTrackDialogComponent, { + width: '50em', + autoFocus: false, + }); + + dialogRef.afterClosed().subscribe(result => { + if (!result) return; + // attempt to extract an id from the result + const createdId = result?.id || result?.track_id || null; + if (createdId) this.viewTrack.emit(createdId); + // refresh list to include the newly-created track + this.loadTracks(); + }); + } +} diff --git a/src/style/typography.scss b/src/style/typography.scss index 6e98787dc..e5a2d5115 100644 --- a/src/style/typography.scss +++ b/src/style/typography.scss @@ -79,6 +79,9 @@ color: rgba(255, 255, 255, 0.6); } } +.description-text { + @extend .placeholder-text; +} .mdc-label { line-height: 24px; padding: 0px !important; From 25f8e6dcc9f51ba847a15de07c8df1d929503d61 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Thu, 16 Apr 2026 15:29:54 -0400 Subject: [PATCH 015/190] chore: formatting --- .../new-track-dialog.component.html | 13 +++++++------ .../dashboard-page/dashboard-page.component.html | 5 ++++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/app/components/new-track-dialog/new-track-dialog.component.html b/src/app/components/new-track-dialog/new-track-dialog.component.html index b1a786e1c..7e5163158 100644 --- a/src/app/components/new-track-dialog/new-track-dialog.component.html +++ b/src/app/components/new-track-dialog/new-track-dialog.component.html @@ -26,13 +26,12 @@

Create New Release Track

Auto-promote Candidates
- Automatically promote Candidates to "Staged" when they meet the threshold. + Automatically promote Candidates to "Staged" when they meet the + threshold.
- - Auto-promote - + Auto-promote
@@ -56,7 +55,8 @@

Create New Release Track

}} - Determines if new revisions to existing member objects are automatically added as candidates. + Determines if new revisions to existing member objects are automatically + added as candidates. @@ -68,7 +68,8 @@

Create New Release Track

}} - Dictates how to handle a new version of an object that is already staged. + Dictates how to handle a new version of an object that is already + staged. diff --git a/src/app/views/dashboard-page/dashboard-page.component.html b/src/app/views/dashboard-page/dashboard-page.component.html index 8a37e0013..6c52cd8ac 100644 --- a/src/app/views/dashboard-page/dashboard-page.component.html +++ b/src/app/views/dashboard-page/dashboard-page.component.html @@ -7,7 +7,10 @@

Dashboard

-