From e96a4cd135fb20b6cc436deef5dbccb992bb3e6f Mon Sep 17 00:00:00 2001 From: ismailbennani Date: Fri, 25 Apr 2025 23:15:08 +0200 Subject: [PATCH 01/25] use sbom in frontend application --- src/FacturXDotNet.WebEditor/.gitignore | 3 + src/FacturXDotNet.WebEditor/package-lock.json | 8 + src/FacturXDotNet.WebEditor/package.json | 3 +- .../scripts/dump-licenses.js | 26 ++- .../about/about-licenses.component.ts | 175 ++++++++++++---- .../src/app/features/about/about.page.ts | 58 +++-- .../src/licenses/licenses.json | 198 ------------------ src/FacturXDotNet.sln.DotSettings.user | 1 + 8 files changed, 214 insertions(+), 258 deletions(-) delete mode 100644 src/FacturXDotNet.WebEditor/src/licenses/licenses.json diff --git a/src/FacturXDotNet.WebEditor/.gitignore b/src/FacturXDotNet.WebEditor/.gitignore index cc7b1413..50a92bcb 100644 --- a/src/FacturXDotNet.WebEditor/.gitignore +++ b/src/FacturXDotNet.WebEditor/.gitignore @@ -40,3 +40,6 @@ testem.log # System files .DS_Store Thumbs.db + +src/dependencies/dependencies.json +src/dependencies/direct-dependencies.json diff --git a/src/FacturXDotNet.WebEditor/package-lock.json b/src/FacturXDotNet.WebEditor/package-lock.json index 7d28246d..80992d0b 100644 --- a/src/FacturXDotNet.WebEditor/package-lock.json +++ b/src/FacturXDotNet.WebEditor/package-lock.json @@ -19,6 +19,7 @@ "@ng-bootstrap/ng-bootstrap": "^18.0.0", "@popperjs/core": "^2.11.8", "@types/bootstrap": "^5.2.10", + "@types/semver": "^7.7.0", "bootstrap": "^5.3.3", "bootstrap-icons": "^1.11.3", "idb": "^8.0.2", @@ -27,6 +28,7 @@ "ngx-markdown": "^19.1.1", "pdfjs-dist": "^5.1.91", "rxjs": "~7.8.0", + "semver": "^7.7.1", "tslib": "^2.3.0", "zone.js": "~0.15.0" }, @@ -5932,6 +5934,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/semver": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", + "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", + "license": "MIT" + }, "node_modules/@types/send": { "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", diff --git a/src/FacturXDotNet.WebEditor/package.json b/src/FacturXDotNet.WebEditor/package.json index b7d73734..9ae7bda6 100644 --- a/src/FacturXDotNet.WebEditor/package.json +++ b/src/FacturXDotNet.WebEditor/package.json @@ -21,6 +21,7 @@ "@ng-bootstrap/ng-bootstrap": "^18.0.0", "@popperjs/core": "^2.11.8", "@types/bootstrap": "^5.2.10", + "@types/semver": "^7.7.0", "bootstrap": "^5.3.3", "bootstrap-icons": "^1.11.3", "idb": "^8.0.2", @@ -29,6 +30,7 @@ "ngx-markdown": "^19.1.1", "pdfjs-dist": "^5.1.91", "rxjs": "~7.8.0", + "semver": "^7.7.1", "tslib": "^2.3.0", "zone.js": "~0.15.0" }, @@ -44,7 +46,6 @@ "karma-coverage": "~2.2.0", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.1.0", - "license-report": "^6.7.2", "nswag": "^14.3.0", "prettier": "3.5.3", "typescript": "~5.7.2" diff --git a/src/FacturXDotNet.WebEditor/scripts/dump-licenses.js b/src/FacturXDotNet.WebEditor/scripts/dump-licenses.js index 03398029..f285fc4e 100644 --- a/src/FacturXDotNet.WebEditor/scripts/dump-licenses.js +++ b/src/FacturXDotNet.WebEditor/scripts/dump-licenses.js @@ -2,16 +2,20 @@ const { execSync } = require("child_process"); const fs = require("fs"); const path = require("path"); -const configFilePath = path.join(__dirname, "..", "license-report-config.json"); -const outputFilePath = path.join(__dirname, "..", "src", "licenses", "licenses.json"); +const dependenciesFileDirectory = path.join(__dirname, "..", "src", "dependencies"); +fs.mkdirSync(dependenciesFileDirectory, { recursive: true }); -execSync("npm i -g license-report", { stdio: "inherit" }); -console.log(); +const dependenciesFilePath = path.join(dependenciesFileDirectory, "dependencies.json"); +const dependenciesFile = fs.openSync(dependenciesFilePath, "w+"); +execSync("npm sbom --sbom-format cyclonedx", { stdio: ["inherit", dependenciesFile, "inherit"] }); +fs.closeSync(dependenciesFile); +console.log(`Dependencies have been written at ${dependenciesFilePath}`); -console.log(`Using configuration at at ${configFilePath}`); - -const outputFile = fs.openSync(outputFilePath, "w+"); -execSync(`license-report --config ${configFilePath}`, { stdio: ["inherit", outputFile, "inherit"] }); -fs.closeSync(outputFile); - -console.log(`Licenses have been written at ${outputFilePath}`); +const packageJson = JSON.parse(fs.readFileSync("package.json", "utf8")); +const directDependenciesNames = [ + ...Object.entries(packageJson.dependencies).map(([name, version]) => ({ name, version })), + ...Object.entries(packageJson.devDependencies).map(([name, version]) => ({ name, version })), +]; +const directDependenciesFilePath = path.join(dependenciesFileDirectory, "direct-dependencies.json"); +fs.writeFileSync(directDependenciesFilePath, JSON.stringify(directDependenciesNames)); +console.log(`Direct dependencies have been written at ${dependenciesFilePath}`); diff --git a/src/FacturXDotNet.WebEditor/src/app/features/about/about-licenses.component.ts b/src/FacturXDotNet.WebEditor/src/app/features/about/about-licenses.component.ts index e2da08f8..5fe88266 100644 --- a/src/FacturXDotNet.WebEditor/src/app/features/about/about-licenses.component.ts +++ b/src/FacturXDotNet.WebEditor/src/app/features/about/about-licenses.component.ts @@ -1,50 +1,155 @@ -import { Component, computed, input, Signal } from '@angular/core'; -import { IPackageDto } from '../../core/api/api.models'; +import { Component, computed, input, signal, Signal } from '@angular/core'; +import semver from 'semver/preload'; +import { NgTemplateOutlet } from '@angular/common'; @Component({ selector: 'app-about-licenses', + imports: [NgTemplateOutlet], template: ` -
- @for (license of licenses(); track license) { -
- {{ license }} ({{ packagesRecord()[license].length }}) -
- @for (p of packagesRecord()[license]; track p.name) { -
- {{ p.name }} - v{{ p.version }} - - - - {{ p.author }} - -
- } -
-
- } +
+ +
+ +
+ +
+
+
+ + +
+ @for (license of record.licenses; track license.license) { +
+ {{ license.license }} ({{ license.packages.length }}) +
    + @for (package_ of license.packages; track package_.name) { +
  • + {{ package_.latest.name }} + + @switch (package_.versions.length) { + @case (0) {} + @case (1) { + v{{ package_.versions[0] }} + } + @default { + @for (version of package_.versions; track version) { + @if ($last) { + v{{ version }} + } @else { + v{{ version }}, + } + } + } + } + + @if (package_.latest.description) { + - + + {{ package_.latest.description }} + + } + @if (package_.latest.author) { + - + + {{ package_.latest.author }} + + } +
  • + } +
+
+ } +
+
`, }) export class AboutLicensesComponent { - packages = input.required(); + packages = input.required(); - protected packagesRecord: Signal> = computed(() => { - const result: Record = {}; + protected directDependenciesRecord: Signal = computed(() => + groupPackages( + this.packages().filter((p) => p.direct), + true, + ), + ); + protected allDependenciesRecord: Signal = computed(() => groupPackages(this.packages())); - for (const p of this.packages()) { - if (!result[p.license]) { - result[p.license] = []; - } + protected activeTab = signal<'direct' | 'all'>('direct'); +} - result[p.license].push(p); +export interface Package { + name: string; + description?: string; + author?: string; + version: string; + license?: string; + link?: string; + direct: boolean; +} + +interface GroupedPackages { + packagesCount: number; + licenses: { + license: string; + packages: { + name: string; + versions: string[]; + latest: Package; + packages: Package[]; + }[]; + }[]; +} + +function groupPackages(packages: Package[], keepLatestOnly: boolean = false): GroupedPackages { + const licenses: Record> = {}; + + for (const p of packages) { + const license = p.license ?? 'N/A'; + + if (!licenses[license]) { + licenses[license] = {}; + } + + if (!licenses[license][p.name]) { + licenses[license][p.name] = []; } - return result; - }); + if (keepLatestOnly) { + if (licenses[license][p.name].every((x) => semver.lt(x.version, p.version))) { + licenses[license][p.name] = [p]; + } + } else { + licenses[license][p.name].push(p); + } + } - protected licenses: Signal = computed(() => { - const result = Object.keys(this.packagesRecord()); - result.sort((a, b) => a.localeCompare(b)); - return result; - }); + return { + packagesCount: Object.values(licenses) + .map((l) => Object.keys(l).length) + .reduce((a, b) => a + b), + licenses: Object.entries(licenses).map(([license, packages]) => ({ + license, + packages: Object.entries(packages) + .map(([name, packages]) => ({ + name, + versions: [...new Set(packages.map((p) => p.version).filter((v) => v !== undefined && v !== null && v != ''))].sort((a, b) => -semver.compare(a, b)), + latest: packages.sort((a, b) => -semver.compare(a.version, b.version))[0], + packages, + })) + .sort((a, b) => a.name.localeCompare(b.name)), + })), + }; } diff --git a/src/FacturXDotNet.WebEditor/src/app/features/about/about.page.ts b/src/FacturXDotNet.WebEditor/src/app/features/about/about.page.ts index 794451db..03293632 100644 --- a/src/FacturXDotNet.WebEditor/src/app/features/about/about.page.ts +++ b/src/FacturXDotNet.WebEditor/src/app/features/about/about.page.ts @@ -1,13 +1,14 @@ import { Component, computed, inject } from '@angular/core'; import { RouterLink } from '@angular/router'; -import { AboutLicensesComponent } from './about-licenses.component'; +import { AboutLicensesComponent, Package } from './about-licenses.component'; import { environment } from '../../../environments/environment'; import { DatePipe, NgOptimizedImage } from '@angular/common'; import { API_BASE_URL } from '../../app.config'; -import licenses from '../../../licenses/licenses.json'; +import dependencies from '../../../dependencies/dependencies.json'; +import directDependencies from '../../../dependencies/direct-dependencies.json'; import { ApiServerStatusComponent } from '../../core/api/components/api-server-status.component'; import { ApiConstantsService } from '../../core/api/services/api-constants.service'; -import { IPackageDto } from '../../core/api/api.models'; +import semver from 'semver/preload'; @Component({ selector: 'app-about', @@ -144,8 +145,9 @@ import { IPackageDto } from '../../core/api/api.models'; }

-

Dependencies

- +
+ + } }
@@ -173,7 +175,8 @@ import { IPackageDto } from '../../core/api/api.models'; on GitHub.

-

Dependencies

+
+ @@ -184,13 +187,17 @@ import { IPackageDto } from '../../core/api/api.models'; imports: [RouterLink, AboutLicensesComponent, DatePipe, ApiServerStatusComponent, NgOptimizedImage], }) export class AboutPage { - protected webEditorPackages: IPackageDto[] = licenses.map((l) => ({ - name: l.name, - author: l.author, - version: l.installedVersion, - license: l.licenseType, - link: l.link, - })); + protected webEditorPackages: Package[] = dependencies.components.map((l) => { + return { + name: l.name, + description: l.description, + author: l.author, + version: l.version, + license: getLicense(l.licenses?.[0]), + link: l.externalReferences.find((r) => r.type === 'website')?.url, + direct: directDependencies.some((d) => l.name === d.name && semver.satisfies(l.version, d.version)), + }; + }); protected webEditorVersion = removeBuildInformation(environment.version); protected webEditorBuildMetadata = extractBuildInformation(environment.version); @@ -217,6 +224,15 @@ export class AboutPage { return extractBuildInformation(apiConstants.build.version); }); + protected dependencies = computed(() => { + const info = this.apiConstantsService.info.value(); + if (info === undefined) { + return []; + } + + return info.dependencies.map((d) => ({ ...d, direct: true })); + }); + protected apiConstants = this.apiConstantsService.info; } @@ -237,3 +253,19 @@ function removeBuildInformation(version: string) { return version.substring(0, indexOfPlus); } + +function getLicense(license: { license: { id: string } } | { expression: string } | undefined): string | undefined { + if (license === undefined) { + return undefined; + } + + if (isExpressionLicense(license)) { + return license.expression; + } + + return license.license.id; +} + +function isExpressionLicense(license: { license: { id: string } } | { expression: string }): license is { expression: string } { + return Object.keys(license).includes('expression'); +} diff --git a/src/FacturXDotNet.WebEditor/src/licenses/licenses.json b/src/FacturXDotNet.WebEditor/src/licenses/licenses.json deleted file mode 100644 index dada8320..00000000 --- a/src/FacturXDotNet.WebEditor/src/licenses/licenses.json +++ /dev/null @@ -1,198 +0,0 @@ -[ - { - "name": "@angular/animations", - "author": "angular", - "installedVersion": "19.2.4", - "licenseType": "MIT", - "link": "git+https://github.com/angular/angular.git" - }, - { - "name": "@angular/common", - "author": "angular", - "installedVersion": "19.2.4", - "licenseType": "MIT", - "link": "git+https://github.com/angular/angular.git" - }, - { - "name": "@angular/compiler", - "author": "angular", - "installedVersion": "19.2.4", - "licenseType": "MIT", - "link": "git+https://github.com/angular/angular.git" - }, - { - "name": "@angular/core", - "author": "angular", - "installedVersion": "19.2.4", - "licenseType": "MIT", - "link": "git+https://github.com/angular/angular.git" - }, - { - "name": "@angular/forms", - "author": "angular", - "installedVersion": "19.2.4", - "licenseType": "MIT", - "link": "git+https://github.com/angular/angular.git" - }, - { - "name": "@angular/platform-browser", - "author": "angular", - "installedVersion": "19.2.4", - "licenseType": "MIT", - "link": "git+https://github.com/angular/angular.git" - }, - { - "name": "@angular/platform-browser-dynamic", - "author": "angular", - "installedVersion": "19.2.4", - "licenseType": "MIT", - "link": "git+https://github.com/angular/angular.git" - }, - { - "name": "@angular/router", - "author": "angular", - "installedVersion": "19.2.4", - "licenseType": "MIT", - "link": "git+https://github.com/angular/angular.git" - }, - { - "name": "@types/bootstrap", - "author": "n/a", - "installedVersion": "5.2.10", - "licenseType": "MIT", - "link": "https://github.com/DefinitelyTyped/DefinitelyTyped.git" - }, - { - "name": "bootstrap", - "author": "The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)", - "installedVersion": "5.3.3", - "licenseType": "MIT", - "link": "git+https://github.com/twbs/bootstrap.git" - }, - { - "name": "bootstrap-icons", - "author": "mdo", - "installedVersion": "1.11.3", - "licenseType": "MIT", - "link": "git+https://github.com/twbs/icons.git" - }, - { - "name": "idb", - "author": "Jake Archibald", - "installedVersion": "8.0.2", - "licenseType": "ISC", - "link": "git://github.com/jakearchibald/idb.git" - }, - { - "name": "rxjs", - "author": "Ben Lesh ", - "installedVersion": "7.8.2", - "licenseType": "Apache-2.0", - "link": "git+https://github.com/reactivex/rxjs.git" - }, - { - "name": "tslib", - "author": "Microsoft Corp.", - "installedVersion": "2.8.1", - "licenseType": "0BSD", - "link": "git+https://github.com/Microsoft/tslib.git" - }, - { - "name": "zone.js", - "author": "Brian Ford", - "installedVersion": "0.15.0", - "licenseType": "MIT", - "link": "git://github.com/angular/angular.git" - }, - { - "name": "@angular-devkit/build-angular", - "author": "Angular Authors", - "installedVersion": "19.2.5", - "licenseType": "MIT", - "link": "git+https://github.com/angular/angular-cli.git" - }, - { - "name": "@angular/cli", - "author": "Angular Authors", - "installedVersion": "19.2.5", - "licenseType": "MIT", - "link": "git+https://github.com/angular/angular-cli.git" - }, - { - "name": "@angular/compiler-cli", - "author": "n/a", - "installedVersion": "19.2.4", - "licenseType": "MIT", - "link": "git+https://github.com/angular/angular.git" - }, - { - "name": "@types/jasmine", - "author": "n/a", - "installedVersion": "5.1.7", - "licenseType": "MIT", - "link": "https://github.com/DefinitelyTyped/DefinitelyTyped.git" - }, - { - "name": "jasmine-core", - "author": "n/a", - "installedVersion": "5.5.0", - "licenseType": "MIT", - "link": "git+https://github.com/jasmine/jasmine.git" - }, - { - "name": "karma", - "author": "Vojta Jína ", - "installedVersion": "6.4.4", - "licenseType": "MIT", - "link": "git://github.com/karma-runner/karma.git" - }, - { - "name": "karma-chrome-launcher", - "author": "Vojta Jina ", - "installedVersion": "3.2.0", - "licenseType": "MIT", - "link": "git://github.com/karma-runner/karma-chrome-launcher.git" - }, - { - "name": "karma-coverage", - "author": "SATO taichi ", - "installedVersion": "2.2.1", - "licenseType": "MIT", - "link": "git://github.com/karma-runner/karma-coverage.git" - }, - { - "name": "karma-jasmine", - "author": "Vojta Jina ", - "installedVersion": "5.1.0", - "licenseType": "MIT", - "link": "git://github.com/karma-runner/karma-jasmine.git" - }, - { - "name": "karma-jasmine-html-reporter", - "author": "David Federman (https://github.com/dfederm)", - "installedVersion": "2.1.0", - "licenseType": "MIT", - "link": "git+https://github.com/dfederm/karma-jasmine-html-reporter.git" - }, - { - "name": "license-report", - "author": "Yaniv Kessler", - "installedVersion": "6.7.2", - "licenseType": "MIT", - "link": "git+https://github.com/kessler/license-report.git" - }, - { - "name": "prettier", - "author": "James Long", - "installedVersion": "3.5.3", - "licenseType": "MIT", - "link": "git+https://github.com/prettier/prettier.git" - }, - { - "name": "typescript", - "author": "Microsoft Corp.", - "installedVersion": "5.7.3", - "licenseType": "Apache-2.0", - "link": "git+https://github.com/microsoft/TypeScript.git" - } -] diff --git a/src/FacturXDotNet.sln.DotSettings.user b/src/FacturXDotNet.sln.DotSettings.user index 520b3860..8da17acf 100644 --- a/src/FacturXDotNet.sln.DotSettings.user +++ b/src/FacturXDotNet.sln.DotSettings.user @@ -38,6 +38,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded From aafd1c63a5fed82bbbebc1df632754063c4c0f70 Mon Sep 17 00:00:00 2001 From: ismailbennani Date: Sat, 26 Apr 2025 00:59:11 +0200 Subject: [PATCH 02/25] allow to fold and unfold license blocks --- .../about/about-licenses.component.ts | 62 +++++++++++++++---- 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/src/FacturXDotNet.WebEditor/src/app/features/about/about-licenses.component.ts b/src/FacturXDotNet.WebEditor/src/app/features/about/about-licenses.component.ts index 5fe88266..15beb03c 100644 --- a/src/FacturXDotNet.WebEditor/src/app/features/about/about-licenses.component.ts +++ b/src/FacturXDotNet.WebEditor/src/app/features/about/about-licenses.component.ts @@ -1,12 +1,13 @@ -import { Component, computed, input, signal, Signal } from '@angular/core'; +import { Component, computed, input, linkedSignal, signal, Signal, WritableSignal } from '@angular/core'; import semver from 'semver/preload'; import { NgTemplateOutlet } from '@angular/common'; +import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; @Component({ selector: 'app-about-licenses', - imports: [NgTemplateOutlet], + imports: [NgTemplateOutlet, NgbTooltip], template: ` -
+
+
+ + +
@@ -32,8 +41,15 @@ import { NgTemplateOutlet } from '@angular/common';
@for (license of record.licenses; track license.license) {
- {{ license.license }} ({{ license.packages.length }}) -
    + + @if (hideLicense()[license.license]()) { + + } @else { + + } + {{ license.license }} ({{ license.packages.length }}) + +
      @for (package_ of license.packages; track package_.name) {
    • {{ package_.latest.name }} @@ -55,18 +71,18 @@ import { NgTemplateOutlet } from '@angular/common'; } } - @if (package_.latest.description) { - - - - {{ package_.latest.description }} - - } @if (package_.latest.author) { - {{ package_.latest.author }} } + + @if (package_.latest.description) { +
      + {{ package_.latest.description }} +
      + }
    • }
    @@ -88,6 +104,30 @@ export class AboutLicensesComponent { protected allDependenciesRecord: Signal = computed(() => groupPackages(this.packages())); protected activeTab = signal<'direct' | 'all'>('direct'); + protected hideLicense: Signal>> = linkedSignal({ + source: this.allDependenciesRecord, + computation: (source, previous) => { + const newValue = previous !== undefined ? { ...previous.value } : {}; + for (const license of source.licenses) { + newValue[license.license] = newValue[license.license] ?? signal(false); + } + return newValue; + }, + }); + + protected collapseAll() { + const hideLicense = this.hideLicense(); + for (const value of Object.values(hideLicense)) { + value.set(true); + } + } + + protected expandAll() { + const hideLicense = this.hideLicense(); + for (const value of Object.values(hideLicense)) { + value.set(false); + } + } } export interface Package { From 84fe46b89ebf82ab3247bea950b1d61174f6a9f3 Mon Sep 17 00:00:00 2001 From: ismailbennani Date: Sat, 26 Apr 2025 01:56:10 +0200 Subject: [PATCH 03/25] add search to dependencies --- src/FacturXDotNet.WebEditor/package-lock.json | 490 +----------------- src/FacturXDotNet.WebEditor/package.json | 1 + .../highlight-text/highlight-text.pipe.ts | 19 + .../about/about-licenses.component.ts | 115 ++-- src/FacturXDotNet.WebEditor/src/styles.css | 5 + 5 files changed, 116 insertions(+), 514 deletions(-) create mode 100644 src/FacturXDotNet.WebEditor/src/app/core/highlight-text/highlight-text.pipe.ts diff --git a/src/FacturXDotNet.WebEditor/package-lock.json b/src/FacturXDotNet.WebEditor/package-lock.json index 80992d0b..b0e64034 100644 --- a/src/FacturXDotNet.WebEditor/package-lock.json +++ b/src/FacturXDotNet.WebEditor/package-lock.json @@ -24,6 +24,7 @@ "bootstrap-icons": "^1.11.3", "idb": "^8.0.2", "marked": "^15.0.8", + "minisearch": "^7.1.2", "ngx-filesize": "^3.0.5", "ngx-markdown": "^19.1.1", "pdfjs-dist": "^5.1.91", @@ -44,7 +45,6 @@ "karma-coverage": "~2.2.0", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.1.0", - "license-report": "^6.7.2", "nswag": "^14.3.0", "prettier": "3.5.3", "typescript": "~5.7.2" @@ -3505,13 +3505,6 @@ "tslib": "2" } }, - "node_modules/@kessler/tableify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@kessler/tableify/-/tableify-1.0.2.tgz", - "integrity": "sha512-e4psVV9Fe2eBfS9xK2rzQ9lE5xS4tARm7EJzDb6sVZy3F+EMyHJ67i0NdBVR9BRyQx7YhogMCbB6R1QwXuBxMg==", - "dev": true, - "license": "MIT" - }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", @@ -5204,13 +5197,6 @@ "yarn": ">= 1.13.0" } }, - "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", - "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", - "dev": true, - "license": "MIT" - }, "node_modules/@sigstore/bundle": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-3.1.0.tgz", @@ -5291,19 +5277,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/@sindresorhus/is": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.0.1.tgz", - "integrity": "sha512-QWLl2P+rsCJeofkDNIT3WFmb6NrRud1SUYW8dIhXK/46XFV8Q/g7Bsvib0Askb0reRLe+WYPeeE+l5cH7SlkuQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, "node_modules/@sindresorhus/merge-streams": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", @@ -5324,19 +5297,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@szmarczak/http-timer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", - "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", - "dev": true, - "license": "MIT", - "dependencies": { - "defer-to-connect": "^2.0.1" - }, - "engines": { - "node": ">=14.16" - } - }, "node_modules/@tufjs/canonical-json": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", @@ -5848,13 +5808,6 @@ "license": "MIT", "optional": true }, - "node_modules/@types/http-cache-semantics": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", @@ -6985,35 +6938,6 @@ "node": ">=18" } }, - "node_modules/cacheable-lookup": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", - "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - } - }, - "node_modules/cacheable-request": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-12.0.1.tgz", - "integrity": "sha512-Yo9wGIQUaAfIbk+qY0X4cDQgCosecfBe3V9NSyeY4qPC2SAkbCS4Xj79VP8WOzitpJUZKc/wsRCYF5ariDIwkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-cache-semantics": "^4.0.4", - "get-stream": "^9.0.1", - "http-cache-semantics": "^4.1.1", - "keyv": "^4.5.4", - "mimic-response": "^4.0.0", - "normalize-url": "^8.0.1", - "responselike": "^3.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -8375,45 +8299,6 @@ } } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/default-browser": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", @@ -8457,16 +8342,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/define-lazy-prop": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", @@ -8847,16 +8722,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eol": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/eol/-/eol-0.10.0.tgz", - "integrity": "sha512-+w3ktYrOphcIqC1XKmhQYvM+o2uxgQFiimL7B6JPZJlWVxf7Lno9e/JWLPIgbHo7DoZ+b7jsf/NzrUcNe6ZTZQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ryanve" - } - }, "node_modules/err-code": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", @@ -9465,16 +9330,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/form-data-encoder": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.0.2.tgz", - "integrity": "sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 18" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -9639,33 +9494,6 @@ "node": ">= 0.4" } }, - "node_modules/get-stdin": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", - "integrity": "sha512-jZV7n6jGE3Gt7fgSTJoz91Ak5MuTLwMwkoYdjxuJ/AmjIsE1UC03y/IWkZCQGEvVNS9qoRNwy5BCqxImv0FVeA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/get-stream": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", - "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sec-ant/readable-stream": "^0.4.1", - "is-stream": "^4.0.1" - }, - "engines": { - "node": ">=18" - }, - "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", @@ -9761,45 +9589,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/got": { - "version": "14.4.7", - "resolved": "https://registry.npmjs.org/got/-/got-14.4.7.tgz", - "integrity": "sha512-DI8zV1231tqiGzOiOzQWDhsBmncFW7oQDH6Zgy6pDPrqJuVZMtoSgPLLsBZQj8Jg4JFfwoOsDA8NGtLQLnIx2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sindresorhus/is": "^7.0.1", - "@szmarczak/http-timer": "^5.0.1", - "cacheable-lookup": "^7.0.0", - "cacheable-request": "^12.0.1", - "decompress-response": "^6.0.0", - "form-data-encoder": "^4.0.2", - "http2-wrapper": "^2.2.1", - "lowercase-keys": "^3.0.0", - "p-cancelable": "^4.0.1", - "responselike": "^3.0.0", - "type-fest": "^4.26.1" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/got/node_modules/type-fest": { - "version": "4.39.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.39.1.tgz", - "integrity": "sha512-uW9qzd66uyHYxwyVBYiwS4Oi0qZyUqwjU+Oevr6ZogYiXt99EOYtwvzMSLw1c3lYo2HzJsep/NB23iEVEgjG/w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -10074,20 +9863,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/http2-wrapper": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", - "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.2.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, "node_modules/https-proxy-agent": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", @@ -10504,19 +10279,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-stream": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", - "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -10773,13 +10535,6 @@ "node": ">=6" } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, "node_modules/json-parse-even-better-errors": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", @@ -11200,16 +10955,6 @@ "node": ">= 12" } }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, "node_modules/khroma": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", @@ -11373,30 +11118,6 @@ "node": ">=0.10.0" } }, - "node_modules/license-report": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/license-report/-/license-report-6.7.2.tgz", - "integrity": "sha512-DWWy7Hmm266CAtx9T+rPNnBwqLNT/Gddc6nToMERQTrsp/caQjrS8PEOl/ymiER24rk12GDLqJqri73mBQj0dg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@kessler/tableify": "^1.0.2", - "debug": "^4.4.0", - "eol": "^0.10.0", - "got": "^14.4.6", - "rc": "^1.2.8", - "semver": "^7.7.1", - "tablemark": "^3.1.0", - "text-table": "^0.2.0", - "visit-values": "^2.0.0" - }, - "bin": { - "license-report": "index.js" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/license-webpack-plugin": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", @@ -11714,29 +11435,6 @@ "node": ">=8.0" } }, - "node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/lowercase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", - "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -12010,19 +11708,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mimic-response": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", - "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/mini-css-extract-plugin": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz", @@ -12214,6 +11899,12 @@ "dev": true, "license": "ISC" }, + "node_modules/minisearch": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.1.2.tgz", + "integrity": "sha512-R1Pd9eF+MD5JYDDSPAp/q1ougKglm14uEkPMvQ/05RGmx6G9wvmLTrTI/Q5iPNJLYqNdsDQ7qTGIcNWR+FrHmA==", + "license": "MIT" + }, "node_modules/minizlib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.1.tgz", @@ -12516,17 +12207,6 @@ "zone.js": "~0.15.0" } }, - "node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, "node_modules/node-addon-api": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", @@ -12755,19 +12435,6 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-url": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", - "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/npm-bundled": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-4.0.0.tgz", @@ -13108,16 +12775,6 @@ "node": ">=0.10.0" } }, - "node_modules/p-cancelable": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-4.0.1.tgz", - "integrity": "sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - } - }, "node_modules/p-limit": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", @@ -13844,19 +13501,6 @@ ], "license": "MIT" }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -13893,29 +13537,6 @@ "node": ">= 0.8" } }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" - }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -14092,13 +13713,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", - "dev": true, - "license": "MIT" - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -14151,22 +13765,6 @@ "node": ">=0.10.0" } }, - "node_modules/responselike": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", - "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", - "dev": true, - "license": "MIT", - "dependencies": { - "lowercase-keys": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/restore-cursor": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", @@ -14598,18 +14196,6 @@ "node": ">= 0.8" } }, - "node_modules/sentence-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", - "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3", - "upper-case-first": "^2.0.2" - } - }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -15222,20 +14808,6 @@ "wbuf": "^1.7.3" } }, - "node_modules/split-text-to-chunks": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/split-text-to-chunks/-/split-text-to-chunks-1.0.0.tgz", - "integrity": "sha512-HLtEwXK/T4l7QZSJ/kOSsZC0o5e2Xg3GzKKFxm0ZexJXw0Bo4CaEl39l7MCSRHk9EOOL5jT8JIDjmhTtcoe6lQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-stdin": "^5.0.1", - "minimist": "^1.2.0" - }, - "bin": { - "wordwrap": "cli.js" - } - }, "node_modules/sprintf-js": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", @@ -15405,16 +14977,6 @@ "node": ">=8" } }, - "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/stylis": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", @@ -15458,20 +15020,6 @@ "node": ">=0.10" } }, - "node_modules/tablemark": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tablemark/-/tablemark-3.1.0.tgz", - "integrity": "sha512-IwO6f0SEzp1Z+zqz/7ANUmeEac4gaNlknWyj/S9aSg11wZmWYnLeyI/xXvEOU88BYUIf8y30y0wxB58xIKrVlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "sentence-case": "^3.0.4", - "split-text-to-chunks": "^1.0.0" - }, - "engines": { - "node": ">=14.16" - } - }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -15637,13 +15185,6 @@ } } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, "node_modules/thingies": { "version": "1.21.0", "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", @@ -15992,16 +15533,6 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/upper-case-first": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", - "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.0.3" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -16060,13 +15591,6 @@ "node": ">= 0.8" } }, - "node_modules/visit-values": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/visit-values/-/visit-values-2.0.0.tgz", - "integrity": "sha512-vLFU70y3D915d611GnHYeHkEmq6ZZETzTH4P1hM6I9E3lBwH2VeBBEESe/bGCY+gAyK0qqLFn5bNFpui/GKmww==", - "dev": true, - "license": "MIT" - }, "node_modules/vite": { "version": "6.2.6", "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz", diff --git a/src/FacturXDotNet.WebEditor/package.json b/src/FacturXDotNet.WebEditor/package.json index 9ae7bda6..32db7d8d 100644 --- a/src/FacturXDotNet.WebEditor/package.json +++ b/src/FacturXDotNet.WebEditor/package.json @@ -26,6 +26,7 @@ "bootstrap-icons": "^1.11.3", "idb": "^8.0.2", "marked": "^15.0.8", + "minisearch": "^7.1.2", "ngx-filesize": "^3.0.5", "ngx-markdown": "^19.1.1", "pdfjs-dist": "^5.1.91", diff --git a/src/FacturXDotNet.WebEditor/src/app/core/highlight-text/highlight-text.pipe.ts b/src/FacturXDotNet.WebEditor/src/app/core/highlight-text/highlight-text.pipe.ts new file mode 100644 index 00000000..0b51f181 --- /dev/null +++ b/src/FacturXDotNet.WebEditor/src/app/core/highlight-text/highlight-text.pipe.ts @@ -0,0 +1,19 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'highlightText', +}) +export class HighlightTextPipe implements PipeTransform { + transform(value: string, args: string[] | undefined): any { + if (args === undefined || args.length === 0) { + return value; + } + + let result = value; + for (const arg of args) { + const regex = new RegExp(arg.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'), 'ig'); + result = result.replace(regex, `$&`); + } + return result; + } +} diff --git a/src/FacturXDotNet.WebEditor/src/app/features/about/about-licenses.component.ts b/src/FacturXDotNet.WebEditor/src/app/features/about/about-licenses.component.ts index 15beb03c..a696854f 100644 --- a/src/FacturXDotNet.WebEditor/src/app/features/about/about-licenses.component.ts +++ b/src/FacturXDotNet.WebEditor/src/app/features/about/about-licenses.component.ts @@ -1,11 +1,16 @@ -import { Component, computed, input, linkedSignal, signal, Signal, WritableSignal } from '@angular/core'; +import { Component, computed, input, linkedSignal, signal, Signal, untracked, WritableSignal } from '@angular/core'; import semver from 'semver/preload'; import { NgTemplateOutlet } from '@angular/common'; import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; +import { FormsModule } from '@angular/forms'; +import MiniSearch from 'minisearch'; +import { HighlightTextPipe } from '../../core/highlight-text/highlight-text.pipe'; + +const UnknownLicenseName = 'N/A'; @Component({ selector: 'app-about-licenses', - imports: [NgTemplateOutlet, NgbTooltip], + imports: [NgTemplateOutlet, NgbTooltip, FormsModule, HighlightTextPipe], template: `
-
- -
-
- +
+
+ +
+
+ +
+
{{ searchResult().packagesCount }} result(s)
+ + + -
+
@for (license of record.licenses; track license.license) {
@@ -52,20 +65,19 @@ import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap';
    @for (package_ of license.packages; track package_.name) {
  • - {{ package_.latest.name }} + @switch (package_.versions.length) { @case (0) {} @case (1) { - v{{ package_.versions[0] }} + } @default { @for (version of package_.versions; track version) { @if ($last) { - v{{ version }} + } @else { - v{{ version }}, + , } } } @@ -73,15 +85,11 @@ import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; @if (package_.latest.author) { - - - {{ package_.latest.author }} - + } @if (package_.latest.description) { -
    - {{ package_.latest.description }} -
    +
    }
  • } @@ -105,16 +113,49 @@ export class AboutLicensesComponent { protected activeTab = signal<'direct' | 'all'>('direct'); protected hideLicense: Signal>> = linkedSignal({ - source: this.allDependenciesRecord, + source: this.packages, computation: (source, previous) => { + const licenses = new Set(source.map((p) => p.license ?? UnknownLicenseName)); + const newValue = previous !== undefined ? { ...previous.value } : {}; - for (const license of source.licenses) { - newValue[license.license] = newValue[license.license] ?? signal(false); + for (const license of licenses) { + newValue[license] = newValue[license] ?? signal(false); } return newValue; }, }); + private miniSearch = computed(() => { + const packages = this.packages().map((p, i) => ({ ...p, index: i })); + const result = new MiniSearch({ + idField: 'index', + fields: ['name', 'description', 'author', 'version', 'license', 'link'], + searchOptions: { prefix: true, fuzzy: true }, + }); + result.addAll(packages); + return result; + }); + protected searchTerm = signal(undefined); + protected searchResult = computed(() => { + const miniSearch = this.miniSearch(); + const allPackages = untracked(() => this.packages()); + + let packages: (Package & { terms: string[] })[]; + const searchTerm = this.searchTerm(); + if (searchTerm === undefined) { + packages = allPackages.map((p) => ({ ...p, terms: [] })); + } else { + packages = miniSearch.search(searchTerm).map((r) => ({ ...allPackages[r.id], terms: r.terms })); + } + + const activeTab = this.activeTab(); + if (activeTab === 'direct') { + packages = packages.filter((p) => p.direct); + } + + return groupPackages(packages); + }); + protected collapseAll() { const hideLicense = this.hideLicense(); for (const value of Object.values(hideLicense)) { @@ -128,6 +169,15 @@ export class AboutLicensesComponent { value.set(false); } } + + protected search(term: string | undefined) { + if (term === undefined || term === '') { + this.searchTerm.set(undefined); + return; + } + + this.searchTerm.set(term); + } } export interface Package { @@ -140,24 +190,24 @@ export interface Package { direct: boolean; } -interface GroupedPackages { +interface GroupedPackages { packagesCount: number; licenses: { license: string; packages: { name: string; versions: string[]; - latest: Package; - packages: Package[]; + latest: TPackage; + packages: TPackage[]; }[]; }[]; } -function groupPackages(packages: Package[], keepLatestOnly: boolean = false): GroupedPackages { - const licenses: Record> = {}; +function groupPackages(packages: TPackage[], keepLatestOnly: boolean = false): GroupedPackages { + const licenses: Record> = {}; for (const p of packages) { - const license = p.license ?? 'N/A'; + const license = p.license ?? UnknownLicenseName; if (!licenses[license]) { licenses[license] = {}; @@ -177,9 +227,12 @@ function groupPackages(packages: Package[], keepLatestOnly: boolean = false): Gr } return { - packagesCount: Object.values(licenses) - .map((l) => Object.keys(l).length) - .reduce((a, b) => a + b), + packagesCount: + Object.values(licenses).length === 0 + ? 0 + : Object.values(licenses) + .map((l) => Object.keys(l).length) + .reduce((a, b) => a + b), licenses: Object.entries(licenses).map(([license, packages]) => ({ license, packages: Object.entries(packages) diff --git a/src/FacturXDotNet.WebEditor/src/styles.css b/src/FacturXDotNet.WebEditor/src/styles.css index d36c68be..eeecfd1a 100644 --- a/src/FacturXDotNet.WebEditor/src/styles.css +++ b/src/FacturXDotNet.WebEditor/src/styles.css @@ -111,3 +111,8 @@ markdown p:last-of-type { .btn-shadow:hover { box-shadow: 0 1rem 3rem rgba(var(--bs-primary-rgb), 0.25); } + +.highlight { + background-color: var(--fx-purple-less-subtle); + font-weight: bold; +} From 09f9b3286afe660a84dfd964c0c134ef920ff9ca Mon Sep 17 00:00:00 2001 From: ismailbennani Date: Sat, 26 Apr 2025 15:42:28 +0200 Subject: [PATCH 04/25] use sbom in api --- src/FacturXDotNet.API/.gitignore | 2 +- .../FacturXDotNet.API.csproj | 6 +- .../Information/InformationController.cs | 43 +++------ src/FacturXDotNet.WebEditor/.gitignore | 3 +- .../scripts/dump-licenses.js | 11 +-- .../src/app/core/api/info.api.ts | 18 +++- .../api/services/api-constants.service.ts | 15 ++- .../src/app/core/sbom.ts | 37 ++++++++ ...s.component.ts => about-sbom.component.ts} | 94 +++++++++---------- .../src/app/features/about/about.page.ts | 49 ++-------- .../src/app/features/about/dependency.ts | 47 ++++++++++ 11 files changed, 181 insertions(+), 144 deletions(-) create mode 100644 src/FacturXDotNet.WebEditor/src/app/core/sbom.ts rename src/FacturXDotNet.WebEditor/src/app/features/about/{about-licenses.component.ts => about-sbom.component.ts} (78%) create mode 100644 src/FacturXDotNet.WebEditor/src/app/features/about/dependency.ts diff --git a/src/FacturXDotNet.API/.gitignore b/src/FacturXDotNet.API/.gitignore index caf0b954..3849bc16 100644 --- a/src/FacturXDotNet.API/.gitignore +++ b/src/FacturXDotNet.API/.gitignore @@ -1,2 +1,2 @@ -Resources/licenses.json +Resources/api.bom.json FacturXDotNet.API.json \ No newline at end of file diff --git a/src/FacturXDotNet.API/FacturXDotNet.API.csproj b/src/FacturXDotNet.API/FacturXDotNet.API.csproj index d255eb6c..0e3da8ac 100644 --- a/src/FacturXDotNet.API/FacturXDotNet.API.csproj +++ b/src/FacturXDotNet.API/FacturXDotNet.API.csproj @@ -46,9 +46,9 @@ - - - + + + diff --git a/src/FacturXDotNet.API/Features/Information/InformationController.cs b/src/FacturXDotNet.API/Features/Information/InformationController.cs index 0f8985bc..9d36a242 100644 --- a/src/FacturXDotNet.API/Features/Information/InformationController.cs +++ b/src/FacturXDotNet.API/Features/Information/InformationController.cs @@ -1,6 +1,5 @@ using FacturXDotNet.API.Configuration; using FacturXDotNet.API.Features.Information.Models; -using FacturXDotNet.API.Features.Information.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; @@ -40,41 +39,21 @@ public static RouteGroupBuilder MapInformationEndpoints(this RouteGroupBuilder r .DisableAntiforgery(); routes.MapGet( - "/dependencies", - async ([FromServices] PackagesService packagesService, CancellationToken cancellationToken = default) => + "/sbom", + () => { - IReadOnlyCollection packages = await packagesService.ReadPackagesAsync(cancellationToken); + string path = Path.GetFullPath(Path.Join("Resources", "api.bom.json")); + if (!File.Exists(path)) + { + return Results.InternalServerError("Could not find SBOM file."); + } - List result = packages.Select( - p => new PackageDto - { - Name = p.PackageName, - Author = string.Join(", ", p.Authors), - Version = p.PackageVersion, - License = p.LicenseType, - Link = p.Repository.Url - } - ) - .ToList(); - - // add the dotnet-project-licenses, which is the tool that is used to extract the licenses file used above - result.Add( - new PackageDto - { - Name = "dotnet-project-licenses", - Author = "Tom Chavakis, Lexy2, senslen", - Version = "2.7.1", - License = "Apache-2.0", - Link = "https://github.com/tomchavakis/nuget-license" - } - ); - - return result; + return Results.File(path, "application/json", "FacturXDotNet-API.sbom.json", enableRangeProcessing: true); } ) - .WithSummary("Dependencies") - .WithDescription("Get information about the dependencies of the API application, especially about their licenses.") - .Produces>() + .WithSummary("SBOM") + .WithDescription("Get the JSON BOM of the API in the CycloneDX format.") + .Produces(StatusCodes.Status200OK, "application/json") .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status500InternalServerError) .DisableAntiforgery(); diff --git a/src/FacturXDotNet.WebEditor/.gitignore b/src/FacturXDotNet.WebEditor/.gitignore index 50a92bcb..08ff15ab 100644 --- a/src/FacturXDotNet.WebEditor/.gitignore +++ b/src/FacturXDotNet.WebEditor/.gitignore @@ -41,5 +41,4 @@ testem.log .DS_Store Thumbs.db -src/dependencies/dependencies.json -src/dependencies/direct-dependencies.json +src/dependencies/sbom.json diff --git a/src/FacturXDotNet.WebEditor/scripts/dump-licenses.js b/src/FacturXDotNet.WebEditor/scripts/dump-licenses.js index f285fc4e..34c11a73 100644 --- a/src/FacturXDotNet.WebEditor/scripts/dump-licenses.js +++ b/src/FacturXDotNet.WebEditor/scripts/dump-licenses.js @@ -5,17 +5,8 @@ const path = require("path"); const dependenciesFileDirectory = path.join(__dirname, "..", "src", "dependencies"); fs.mkdirSync(dependenciesFileDirectory, { recursive: true }); -const dependenciesFilePath = path.join(dependenciesFileDirectory, "dependencies.json"); +const dependenciesFilePath = path.join(dependenciesFileDirectory, "sbom.json"); const dependenciesFile = fs.openSync(dependenciesFilePath, "w+"); execSync("npm sbom --sbom-format cyclonedx", { stdio: ["inherit", dependenciesFile, "inherit"] }); fs.closeSync(dependenciesFile); console.log(`Dependencies have been written at ${dependenciesFilePath}`); - -const packageJson = JSON.parse(fs.readFileSync("package.json", "utf8")); -const directDependenciesNames = [ - ...Object.entries(packageJson.dependencies).map(([name, version]) => ({ name, version })), - ...Object.entries(packageJson.devDependencies).map(([name, version]) => ({ name, version })), -]; -const directDependenciesFilePath = path.join(dependenciesFileDirectory, "direct-dependencies.json"); -fs.writeFileSync(directDependenciesFilePath, JSON.stringify(directDependenciesNames)); -console.log(`Direct dependencies have been written at ${dependenciesFilePath}`); diff --git a/src/FacturXDotNet.WebEditor/src/app/core/api/info.api.ts b/src/FacturXDotNet.WebEditor/src/app/core/api/info.api.ts index cc739e9a..18410c3c 100644 --- a/src/FacturXDotNet.WebEditor/src/app/core/api/info.api.ts +++ b/src/FacturXDotNet.WebEditor/src/app/core/api/info.api.ts @@ -1,6 +1,6 @@ import { inject, Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; -import { Observable } from 'rxjs'; +import { map, Observable } from 'rxjs'; import { API_BASE_URL } from '../../app.config'; import { IBuildInformationDto, IHostingInformationDto, IPackageDto } from './api.models'; @@ -21,8 +21,18 @@ export class InfoApi { return this.httpClient.get(url); } - getDependencies(): Observable { - const url = `${this.baseUrl}/info/dependencies`; - return this.httpClient.get(url); + getSbom(): Observable { + const url = `${this.baseUrl}/info/sbom`; + return this.httpClient.get(url, { observe: 'response', responseType: 'blob' }).pipe( + map((response) => { + if (response.body === null) { + throw new Error('No response body'); + } + + const contentDisposition = response.headers.get('content-disposition'); + const filename = contentDisposition?.split(';')[1].split('filename')[1].split('=')[1].trim(); + return new File([response.body], filename ?? 'FacturXDotNet-API.sbom.json', { type: 'application/json' }); + }), + ); } } diff --git a/src/FacturXDotNet.WebEditor/src/app/core/api/services/api-constants.service.ts b/src/FacturXDotNet.WebEditor/src/app/core/api/services/api-constants.service.ts index ec5289b5..bd20d9f2 100644 --- a/src/FacturXDotNet.WebEditor/src/app/core/api/services/api-constants.service.ts +++ b/src/FacturXDotNet.WebEditor/src/app/core/api/services/api-constants.service.ts @@ -1,7 +1,8 @@ import { inject, Injectable } from '@angular/core'; import { InfoApi } from '../info.api'; import { rxResource } from '@angular/core/rxjs-interop'; -import { forkJoin } from 'rxjs'; +import { forkJoin, map, switchMap } from 'rxjs'; +import { Sbom } from '../../sbom'; @Injectable({ providedIn: 'root', @@ -10,6 +11,16 @@ export class ApiConstantsService { private infoApi = inject(InfoApi); info = rxResource({ - loader: () => forkJoin({ build: this.infoApi.getBuildInformation(), hosting: this.infoApi.getHostingInformation(), dependencies: this.infoApi.getDependencies() }), + loader: () => + forkJoin({ + build: this.infoApi.getBuildInformation(), + hosting: this.infoApi.getHostingInformation(), + sbom: this.infoApi.getSbom().pipe( + switchMap((sbomFile) => { + return sbomFile.text(); + }), + map((sbomContent) => JSON.parse(sbomContent) as Sbom), + ), + }), }); } diff --git a/src/FacturXDotNet.WebEditor/src/app/core/sbom.ts b/src/FacturXDotNet.WebEditor/src/app/core/sbom.ts new file mode 100644 index 00000000..10c96252 --- /dev/null +++ b/src/FacturXDotNet.WebEditor/src/app/core/sbom.ts @@ -0,0 +1,37 @@ +export interface Sbom { + readonly version: number; + readonly metadata: { + readonly component: SbomComponent; + }; + readonly components: SbomComponent[]; + readonly dependencies: { readonly ref: string; readonly dependsOn: string[] }[]; +} + +export interface SbomComponent { + readonly 'bom-ref': string; + readonly name: string; + readonly version: string; + readonly author?: string; + readonly description?: string; + readonly licenses?: SbomLicense[]; + readonly externalReferences: SbomExternalReference[]; +} + +export type SbomLicense = SbomLicenseId | SbomLicenseExpression; + +export interface SbomLicenseId { + readonly license: { readonly id: string }; +} + +export interface SbomLicenseExpression { + readonly expression: string; +} + +export function isSbomLicenseExpression(license: SbomLicense): license is SbomLicenseExpression { + return Object.keys(license).includes('expression'); +} + +export interface SbomExternalReference { + readonly type: string; + readonly url: string; +} diff --git a/src/FacturXDotNet.WebEditor/src/app/features/about/about-licenses.component.ts b/src/FacturXDotNet.WebEditor/src/app/features/about/about-sbom.component.ts similarity index 78% rename from src/FacturXDotNet.WebEditor/src/app/features/about/about-licenses.component.ts rename to src/FacturXDotNet.WebEditor/src/app/features/about/about-sbom.component.ts index a696854f..18d1531f 100644 --- a/src/FacturXDotNet.WebEditor/src/app/features/about/about-licenses.component.ts +++ b/src/FacturXDotNet.WebEditor/src/app/features/about/about-sbom.component.ts @@ -5,6 +5,9 @@ import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; import { FormsModule } from '@angular/forms'; import MiniSearch from 'minisearch'; import { HighlightTextPipe } from '../../core/highlight-text/highlight-text.pipe'; +import sbom from '../../../dependencies/sbom.json'; +import { Sbom, SbomComponent } from '../../core/sbom'; +import { Dependency, extractDependenciesFromSbom } from './dependency'; const UnknownLicenseName = 'N/A'; @@ -16,13 +19,11 @@ const UnknownLicenseName = 'N/A';
    @@ -54,15 +55,15 @@ const UnknownLicenseName = 'N/A';
    @for (license of record.licenses; track license.license) {
    - - @if (hideLicense()[license.license]()) { + + @if (collapsedBlocks()[license.license]()) { } @else { } {{ license.license }} ({{ license.packages.length }}) -
      +
        @for (package_ of license.packages; track package_.name) {
      • @@ -100,33 +101,29 @@ const UnknownLicenseName = 'N/A'; `, }) -export class AboutLicensesComponent { - packages = input.required(); +export class AboutSbomComponent { + sbom = input.required(); + + protected dependencies = computed(() => extractDependenciesFromSbom(this.sbom())); + + protected directDependenciesCount = computed(() => { + const names = new Set( + this.dependencies() + .filter((d) => d.direct) + .map((d) => d.name), + ); + return names.size; + }); - protected directDependenciesRecord: Signal = computed(() => - groupPackages( - this.packages().filter((p) => p.direct), - true, - ), - ); - protected allDependenciesRecord: Signal = computed(() => groupPackages(this.packages())); + protected allDependenciesCount = computed(() => { + const names = new Set(this.dependencies().map((d) => d.name)); + return names.size; + }); protected activeTab = signal<'direct' | 'all'>('direct'); - protected hideLicense: Signal>> = linkedSignal({ - source: this.packages, - computation: (source, previous) => { - const licenses = new Set(source.map((p) => p.license ?? UnknownLicenseName)); - - const newValue = previous !== undefined ? { ...previous.value } : {}; - for (const license of licenses) { - newValue[license] = newValue[license] ?? signal(false); - } - return newValue; - }, - }); private miniSearch = computed(() => { - const packages = this.packages().map((p, i) => ({ ...p, index: i })); + const packages = this.dependencies().map((p, i) => ({ ...p, index: i })); const result = new MiniSearch({ idField: 'index', fields: ['name', 'description', 'author', 'version', 'license', 'link'], @@ -138,9 +135,9 @@ export class AboutLicensesComponent { protected searchTerm = signal(undefined); protected searchResult = computed(() => { const miniSearch = this.miniSearch(); - const allPackages = untracked(() => this.packages()); + const allPackages = untracked(() => this.dependencies()); - let packages: (Package & { terms: string[] })[]; + let packages: (Dependency & { terms: string[] })[]; const searchTerm = this.searchTerm(); if (searchTerm === undefined) { packages = allPackages.map((p) => ({ ...p, terms: [] })); @@ -156,16 +153,29 @@ export class AboutLicensesComponent { return groupPackages(packages); }); + protected collapsedBlocks: Signal>> = linkedSignal({ + source: this.dependencies, + computation: (source, previous) => { + const licenses = new Set(source.map((p) => p.license ?? UnknownLicenseName)); + + const newValue = previous !== undefined ? { ...previous.value } : {}; + for (const license of licenses) { + newValue[license] = newValue[license] ?? signal(false); + } + return newValue; + }, + }); + protected collapseAll() { - const hideLicense = this.hideLicense(); - for (const value of Object.values(hideLicense)) { + const collapsedBlocks = this.collapsedBlocks(); + for (const value of Object.values(collapsedBlocks)) { value.set(true); } } protected expandAll() { - const hideLicense = this.hideLicense(); - for (const value of Object.values(hideLicense)) { + const collapsedBlocks = this.collapsedBlocks(); + for (const value of Object.values(collapsedBlocks)) { value.set(false); } } @@ -180,17 +190,7 @@ export class AboutLicensesComponent { } } -export interface Package { - name: string; - description?: string; - author?: string; - version: string; - license?: string; - link?: string; - direct: boolean; -} - -interface GroupedPackages { +interface DependenciesGroupedByLicensesThenName { packagesCount: number; licenses: { license: string; @@ -203,7 +203,7 @@ interface GroupedPackages { }[]; } -function groupPackages(packages: TPackage[], keepLatestOnly: boolean = false): GroupedPackages { +function groupPackages(packages: TPackage[], keepLatestOnly: boolean = false): DependenciesGroupedByLicensesThenName { const licenses: Record> = {}; for (const p of packages) { diff --git a/src/FacturXDotNet.WebEditor/src/app/features/about/about.page.ts b/src/FacturXDotNet.WebEditor/src/app/features/about/about.page.ts index 03293632..40204cae 100644 --- a/src/FacturXDotNet.WebEditor/src/app/features/about/about.page.ts +++ b/src/FacturXDotNet.WebEditor/src/app/features/about/about.page.ts @@ -1,14 +1,12 @@ import { Component, computed, inject } from '@angular/core'; import { RouterLink } from '@angular/router'; -import { AboutLicensesComponent, Package } from './about-licenses.component'; +import { AboutSbomComponent } from './about-sbom.component'; import { environment } from '../../../environments/environment'; import { DatePipe, NgOptimizedImage } from '@angular/common'; import { API_BASE_URL } from '../../app.config'; -import dependencies from '../../../dependencies/dependencies.json'; -import directDependencies from '../../../dependencies/direct-dependencies.json'; +import sbom from '../../../dependencies/sbom.json'; import { ApiServerStatusComponent } from '../../core/api/components/api-server-status.component'; import { ApiConstantsService } from '../../core/api/services/api-constants.service'; -import semver from 'semver/preload'; @Component({ selector: 'app-about', @@ -147,7 +145,7 @@ import semver from 'semver/preload';
        - + } }
    @@ -177,27 +175,17 @@ import semver from 'semver/preload';
    - +
`, - imports: [RouterLink, AboutLicensesComponent, DatePipe, ApiServerStatusComponent, NgOptimizedImage], + imports: [RouterLink, AboutSbomComponent, DatePipe, ApiServerStatusComponent, NgOptimizedImage], }) export class AboutPage { - protected webEditorPackages: Package[] = dependencies.components.map((l) => { - return { - name: l.name, - description: l.description, - author: l.author, - version: l.version, - license: getLicense(l.licenses?.[0]), - link: l.externalReferences.find((r) => r.type === 'website')?.url, - direct: directDependencies.some((d) => l.name === d.name && semver.satisfies(l.version, d.version)), - }; - }); + protected webEditorSbom = sbom; protected webEditorVersion = removeBuildInformation(environment.version); protected webEditorBuildMetadata = extractBuildInformation(environment.version); @@ -224,15 +212,6 @@ export class AboutPage { return extractBuildInformation(apiConstants.build.version); }); - protected dependencies = computed(() => { - const info = this.apiConstantsService.info.value(); - if (info === undefined) { - return []; - } - - return info.dependencies.map((d) => ({ ...d, direct: true })); - }); - protected apiConstants = this.apiConstantsService.info; } @@ -253,19 +232,3 @@ function removeBuildInformation(version: string) { return version.substring(0, indexOfPlus); } - -function getLicense(license: { license: { id: string } } | { expression: string } | undefined): string | undefined { - if (license === undefined) { - return undefined; - } - - if (isExpressionLicense(license)) { - return license.expression; - } - - return license.license.id; -} - -function isExpressionLicense(license: { license: { id: string } } | { expression: string }): license is { expression: string } { - return Object.keys(license).includes('expression'); -} diff --git a/src/FacturXDotNet.WebEditor/src/app/features/about/dependency.ts b/src/FacturXDotNet.WebEditor/src/app/features/about/dependency.ts new file mode 100644 index 00000000..b7364ea1 --- /dev/null +++ b/src/FacturXDotNet.WebEditor/src/app/features/about/dependency.ts @@ -0,0 +1,47 @@ +import { isSbomLicenseExpression, Sbom, SbomComponent, SbomLicense, SbomLicenseExpression } from '../../core/sbom'; + +export interface Dependency { + name: string; + description?: string; + author?: string; + version: string; + license?: string; + link?: string; + direct: boolean; +} + +export function extractDependenciesFromSbom(sbom: Sbom) { + return sbom.components.map((component) => { + return { + name: component.name, + description: component.description, + author: component.author, + version: component.version, + license: getLicense(component.licenses?.[0]), + link: component.externalReferences.find((r) => r.type === 'website')?.url, + direct: isDirectDependency(sbom, component), + }; + }); +} + +function getLicense(license: SbomLicense | undefined): string | undefined { + if (license === undefined) { + return undefined; + } + + if (isSbomLicenseExpression(license)) { + return license.expression; + } + + return license.license.id; +} + +function isDirectDependency(sbom: Sbom, component: SbomComponent): boolean { + const thisComponent = sbom.metadata.component['bom-ref']; + const dependencies = sbom.dependencies.find((d) => d.ref === thisComponent); + if (dependencies === undefined) { + return false; + } + + return dependencies.dependsOn.includes(component['bom-ref']); +} From 6b30617785071be262bd5d58749f7af1631904b1 Mon Sep 17 00:00:00 2001 From: ismailbennani Date: Sat, 26 Apr 2025 15:56:39 +0200 Subject: [PATCH 05/25] improve frontend layout --- .../features/about/about-sbom.component.ts | 45 ++++++++------ .../src/app/features/about/dependency.ts | 58 ++++++++++++++++--- 2 files changed, 77 insertions(+), 26 deletions(-) diff --git a/src/FacturXDotNet.WebEditor/src/app/features/about/about-sbom.component.ts b/src/FacturXDotNet.WebEditor/src/app/features/about/about-sbom.component.ts index 18d1531f..dcbd364f 100644 --- a/src/FacturXDotNet.WebEditor/src/app/features/about/about-sbom.component.ts +++ b/src/FacturXDotNet.WebEditor/src/app/features/about/about-sbom.component.ts @@ -5,12 +5,9 @@ import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; import { FormsModule } from '@angular/forms'; import MiniSearch from 'minisearch'; import { HighlightTextPipe } from '../../core/highlight-text/highlight-text.pipe'; -import sbom from '../../../dependencies/sbom.json'; -import { Sbom, SbomComponent } from '../../core/sbom'; +import { Sbom } from '../../core/sbom'; import { Dependency, extractDependenciesFromSbom } from './dependency'; -const UnknownLicenseName = 'N/A'; - @Component({ selector: 'app-about-licenses', imports: [NgTemplateOutlet, NgbTooltip, FormsModule, HighlightTextPipe], @@ -61,7 +58,13 @@ const UnknownLicenseName = 'N/A'; } @else { } - {{ license.license }} ({{ license.packages.length }}) + + @if (license.license === '') { + Unknown + } @else { + {{ license.license }} + } + ({{ license.packages.length }})
    @for (package_ of license.packages; track package_.name) { @@ -156,7 +159,7 @@ export class AboutSbomComponent { protected collapsedBlocks: Signal>> = linkedSignal({ source: this.dependencies, computation: (source, previous) => { - const licenses = new Set(source.map((p) => p.license ?? UnknownLicenseName)); + const licenses = new Set(source.map((p) => licenseNameOrDefault(p.license))); const newValue = previous !== undefined ? { ...previous.value } : {}; for (const license of licenses) { @@ -207,7 +210,7 @@ function groupPackages(packages: TPackage[], keepLa const licenses: Record> = {}; for (const p of packages) { - const license = p.license ?? UnknownLicenseName; + const license = licenseNameOrDefault(p.license); if (!licenses[license]) { licenses[license] = {}; @@ -233,16 +236,22 @@ function groupPackages(packages: TPackage[], keepLa : Object.values(licenses) .map((l) => Object.keys(l).length) .reduce((a, b) => a + b), - licenses: Object.entries(licenses).map(([license, packages]) => ({ - license, - packages: Object.entries(packages) - .map(([name, packages]) => ({ - name, - versions: [...new Set(packages.map((p) => p.version).filter((v) => v !== undefined && v !== null && v != ''))].sort((a, b) => -semver.compare(a, b)), - latest: packages.sort((a, b) => -semver.compare(a.version, b.version))[0], - packages, - })) - .sort((a, b) => a.name.localeCompare(b.name)), - })), + licenses: Object.entries(licenses) + .map(([license, packages]) => ({ + license, + packages: Object.entries(packages) + .map(([name, packages]) => ({ + name, + versions: [...new Set(packages.map((p) => p.version).filter((v) => v !== undefined && v !== null && v != ''))].sort((a, b) => -semver.compare(a, b)), + latest: packages.sort((a, b) => -semver.compare(a.version, b.version))[0], + packages, + })) + .sort((a, b) => a.name.localeCompare(b.name)), + })) + .sort((a, b) => (a.license === '' && b.license === '' ? 0 : a.license === '' ? 1 : b.license === '' ? -1 : a.license.localeCompare(b.license))), }; } + +function licenseNameOrDefault(name: string | undefined): string { + return name === undefined || name === '' ? '' : name; +} diff --git a/src/FacturXDotNet.WebEditor/src/app/features/about/dependency.ts b/src/FacturXDotNet.WebEditor/src/app/features/about/dependency.ts index b7364ea1..ab5ac8d3 100644 --- a/src/FacturXDotNet.WebEditor/src/app/features/about/dependency.ts +++ b/src/FacturXDotNet.WebEditor/src/app/features/about/dependency.ts @@ -1,4 +1,4 @@ -import { isSbomLicenseExpression, Sbom, SbomComponent, SbomLicense, SbomLicenseExpression } from '../../core/sbom'; +import { isSbomLicenseExpression, Sbom, SbomComponent, SbomExternalReference, SbomLicense } from '../../core/sbom'; export interface Dependency { name: string; @@ -17,23 +17,44 @@ export function extractDependenciesFromSbom(sbom: Sbom) { description: component.description, author: component.author, version: component.version, - license: getLicense(component.licenses?.[0]), - link: component.externalReferences.find((r) => r.type === 'website')?.url, + license: getLicense(component.licenses), + link: getLink(component.externalReferences), direct: isDirectDependency(sbom, component), }; }); } -function getLicense(license: SbomLicense | undefined): string | undefined { - if (license === undefined) { +function getLicense(licenses: SbomLicense[] | undefined): string | undefined { + if (licenses === undefined) { return undefined; } - if (isSbomLicenseExpression(license)) { - return license.expression; + const licenseNames = licenses.map((license) => { + if (isSbomLicenseExpression(license)) { + if (license.expression.startsWith('(') && license.expression.endsWith(')')) { + return license.expression.substring(1, license.expression.length - 2); + } + + return license.expression; + } + + return license.license.id; + }); + + return licenseNames.join(' OR '); +} + +function getLink(externalReferences: SbomExternalReference[] | undefined): string | undefined { + if (externalReferences === undefined) { + return undefined; + } + + const vcsLink = externalReferences.find((r) => r.type === 'vcs')?.url; + if (vcsLink !== undefined) { + return getRepositoryUrl(vcsLink); } - return license.license.id; + return externalReferences.find((r) => r.type === 'website')?.url; } function isDirectDependency(sbom: Sbom, component: SbomComponent): boolean { @@ -45,3 +66,24 @@ function isDirectDependency(sbom: Sbom, component: SbomComponent): boolean { return dependencies.dependsOn.includes(component['bom-ref']); } + +const gitPlusUrlRegExp = new RegExp(/git\+(.*)\.git/); +const gitSchemeUrlRegExp = new RegExp(/git:\/\/(.*)\.git/); + +function getRepositoryUrl(url: string): string { + if (url === undefined || url === null) { + return url; + } + + const gitPlusUrlRegExpMatch = url.match(gitPlusUrlRegExp); + if (gitPlusUrlRegExpMatch !== null) { + return gitPlusUrlRegExpMatch[1]; + } + + const gitSchemeUrlRegExpMatch = url.match(gitSchemeUrlRegExp); + if (gitSchemeUrlRegExpMatch !== null) { + return `https://${gitSchemeUrlRegExpMatch[1]}`; + } + + return url; +} From 85c109fc33f83622fb9747447fdfd881a8d908df Mon Sep 17 00:00:00 2001 From: ismailbennani Date: Sat, 26 Apr 2025 16:02:05 +0200 Subject: [PATCH 06/25] add download button --- .../src/app/features/about/about-sbom.component.ts | 13 +++++++++++++ .../src/app/features/about/about.page.ts | 8 ++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/FacturXDotNet.WebEditor/src/app/features/about/about-sbom.component.ts b/src/FacturXDotNet.WebEditor/src/app/features/about/about-sbom.component.ts index dcbd364f..07c23255 100644 --- a/src/FacturXDotNet.WebEditor/src/app/features/about/about-sbom.component.ts +++ b/src/FacturXDotNet.WebEditor/src/app/features/about/about-sbom.component.ts @@ -7,6 +7,7 @@ import MiniSearch from 'minisearch'; import { HighlightTextPipe } from '../../core/highlight-text/highlight-text.pipe'; import { Sbom } from '../../core/sbom'; import { Dependency, extractDependenciesFromSbom } from './dependency'; +import { downloadBlob, downloadFile } from '../../core/utils/download-blob'; @Component({ selector: 'app-about-licenses', @@ -30,6 +31,9 @@ import { Dependency, extractDependenciesFromSbom } from './dependency'; +
@@ -106,6 +110,7 @@ import { Dependency, extractDependenciesFromSbom } from './dependency'; }) export class AboutSbomComponent { sbom = input.required(); + sbomName = input(); protected dependencies = computed(() => extractDependenciesFromSbom(this.sbom())); @@ -183,6 +188,14 @@ export class AboutSbomComponent { } } + protected downloadSbom() { + const sbom = this.sbom(); + const name = this.sbomName(); + const serialized = JSON.stringify(sbom); + const file = new File([serialized], name ?? 'sbom.json', { type: 'application/json' }); + downloadFile(file); + } + protected search(term: string | undefined) { if (term === undefined || term === '') { this.searchTerm.set(undefined); diff --git a/src/FacturXDotNet.WebEditor/src/app/features/about/about.page.ts b/src/FacturXDotNet.WebEditor/src/app/features/about/about.page.ts index 40204cae..a4688689 100644 --- a/src/FacturXDotNet.WebEditor/src/app/features/about/about.page.ts +++ b/src/FacturXDotNet.WebEditor/src/app/features/about/about.page.ts @@ -93,7 +93,7 @@ import { ApiConstantsService } from '../../core/api/services/api-constants.servi
-
+

API

@@ -145,14 +145,14 @@ import { ApiConstantsService } from '../../core/api/services/api-constants.servi
- + } }
-
+

Web Editor

@@ -175,7 +175,7 @@ import { ApiConstantsService } from '../../core/api/services/api-constants.servi
- +
From 0bbd6b3217c9ad6242e4fbc9bacb81203dd7d472 Mon Sep 17 00:00:00 2001 From: ismailbennani Date: Sat, 26 Apr 2025 16:10:35 +0200 Subject: [PATCH 07/25] improve layout on small screens --- .../src/app/features/about/about-sbom.component.ts | 13 ++++++------- .../src/app/features/about/about.page.ts | 8 ++------ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/FacturXDotNet.WebEditor/src/app/features/about/about-sbom.component.ts b/src/FacturXDotNet.WebEditor/src/app/features/about/about-sbom.component.ts index 07c23255..21d1dbfd 100644 --- a/src/FacturXDotNet.WebEditor/src/app/features/about/about-sbom.component.ts +++ b/src/FacturXDotNet.WebEditor/src/app/features/about/about-sbom.component.ts @@ -13,18 +13,17 @@ import { downloadBlob, downloadFile } from '../../core/utils/download-blob'; selector: 'app-about-licenses', imports: [NgTemplateOutlet, NgbTooltip, FormsModule, HighlightTextPipe], template: ` -
+
Dependencies
+
-
+
@@ -37,7 +36,7 @@ import { downloadBlob, downloadFile } from '../../core/utils/download-blob';
-
+
-
+

API

@@ -143,8 +143,6 @@ import { ApiConstantsService } from '../../core/api/services/api-constants.servi }

-
- } } @@ -152,7 +150,7 @@ import { ApiConstantsService } from '../../core/api/services/api-constants.servi
-
+

Web Editor

@@ -173,8 +171,6 @@ import { ApiConstantsService } from '../../core/api/services/api-constants.servi on GitHub.

-
-
From ada8016edc28cacbc2f26d41373cb424f069c26d Mon Sep 17 00:00:00 2001 From: ismailbennani Date: Sat, 26 Apr 2025 16:19:53 +0200 Subject: [PATCH 08/25] format package descriptions as markdown --- .../app/core/escape-html/escape-html.pipe.ts | 10 ++++++++++ .../features/about/about-sbom.component.ts | 20 ++++++++++++------- 2 files changed, 23 insertions(+), 7 deletions(-) create mode 100644 src/FacturXDotNet.WebEditor/src/app/core/escape-html/escape-html.pipe.ts diff --git a/src/FacturXDotNet.WebEditor/src/app/core/escape-html/escape-html.pipe.ts b/src/FacturXDotNet.WebEditor/src/app/core/escape-html/escape-html.pipe.ts new file mode 100644 index 00000000..f917deb0 --- /dev/null +++ b/src/FacturXDotNet.WebEditor/src/app/core/escape-html/escape-html.pipe.ts @@ -0,0 +1,10 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'escapeHtml', +}) +export class EscapeHtmlPipe implements PipeTransform { + transform(value: string): string { + return value.replaceAll('<', '<').replaceAll('>', '>'); + } +} diff --git a/src/FacturXDotNet.WebEditor/src/app/features/about/about-sbom.component.ts b/src/FacturXDotNet.WebEditor/src/app/features/about/about-sbom.component.ts index 21d1dbfd..a1f1eaf1 100644 --- a/src/FacturXDotNet.WebEditor/src/app/features/about/about-sbom.component.ts +++ b/src/FacturXDotNet.WebEditor/src/app/features/about/about-sbom.component.ts @@ -8,10 +8,12 @@ import { HighlightTextPipe } from '../../core/highlight-text/highlight-text.pipe import { Sbom } from '../../core/sbom'; import { Dependency, extractDependenciesFromSbom } from './dependency'; import { downloadBlob, downloadFile } from '../../core/utils/download-blob'; +import { MarkdownComponent } from 'ngx-markdown'; +import { EscapeHtmlPipe } from '../../core/escape-html/escape-html.pipe'; @Component({ selector: 'app-about-licenses', - imports: [NgTemplateOutlet, NgbTooltip, FormsModule, HighlightTextPipe], + imports: [NgTemplateOutlet, NgbTooltip, FormsModule, HighlightTextPipe, MarkdownComponent, EscapeHtmlPipe], template: `
Dependencies
@@ -72,19 +74,19 @@ import { downloadBlob, downloadFile } from '../../core/utils/download-blob';
    @for (package_ of license.packages; track package_.name) {
  • - + @switch (package_.versions.length) { @case (0) {} @case (1) { - + } @default { @for (version of package_.versions; track version) { @if ($last) { - + } @else { - , + , } } } @@ -92,11 +94,15 @@ import { downloadBlob, downloadFile } from '../../core/utils/download-blob'; @if (package_.latest.author) { - - + } @if (package_.latest.description) { -
    +

    + + {{ package_.latest.description | escapeHtml | highlightText: package_.latest.terms }} + +

    }
  • } From 79377bc646e5228e6377326ccf3cb4fd4c470f66 Mon Sep 17 00:00:00 2001 From: ismailbennani Date: Sat, 26 Apr 2025 19:38:44 +0200 Subject: [PATCH 09/25] improve error messages when api is unreachable or api sbom is not found --- .../src/app/core/api/api-errors.ts | 13 ++++++ .../api/services/api-constants.service.ts | 20 ++++----- .../src/app/core/toasts/toast.service.ts | 26 ++++------- .../features/about/about-sbom.component.ts | 4 +- .../src/app/features/about/about.page.ts | 45 ++++++++++++++----- .../src/app/features/editor/editor.page.ts | 42 ++++++++--------- 6 files changed, 87 insertions(+), 63 deletions(-) create mode 100644 src/FacturXDotNet.WebEditor/src/app/core/api/api-errors.ts diff --git a/src/FacturXDotNet.WebEditor/src/app/core/api/api-errors.ts b/src/FacturXDotNet.WebEditor/src/app/core/api/api-errors.ts new file mode 100644 index 00000000..1dbd030e --- /dev/null +++ b/src/FacturXDotNet.WebEditor/src/app/core/api/api-errors.ts @@ -0,0 +1,13 @@ +import { HttpErrorResponse } from '@angular/common/http'; + +export function getApiErrorMessage(err: unknown): string { + if (err instanceof HttpErrorResponse) { + if (err.status === 0) { + return "Can't connect to server. Please check your internet connection."; + } + + return err.message; + } + + return 'Unknown error'; +} diff --git a/src/FacturXDotNet.WebEditor/src/app/core/api/services/api-constants.service.ts b/src/FacturXDotNet.WebEditor/src/app/core/api/services/api-constants.service.ts index bd20d9f2..7e31579b 100644 --- a/src/FacturXDotNet.WebEditor/src/app/core/api/services/api-constants.service.ts +++ b/src/FacturXDotNet.WebEditor/src/app/core/api/services/api-constants.service.ts @@ -1,7 +1,7 @@ import { inject, Injectable } from '@angular/core'; import { InfoApi } from '../info.api'; import { rxResource } from '@angular/core/rxjs-interop'; -import { forkJoin, map, switchMap } from 'rxjs'; +import { map, switchMap } from 'rxjs'; import { Sbom } from '../../sbom'; @Injectable({ @@ -10,17 +10,13 @@ import { Sbom } from '../../sbom'; export class ApiConstantsService { private infoApi = inject(InfoApi); - info = rxResource({ + buildInfo = rxResource({ loader: () => this.infoApi.getBuildInformation() }); + hostingInfo = rxResource({ loader: () => this.infoApi.getHostingInformation() }); + sbom = rxResource({ loader: () => - forkJoin({ - build: this.infoApi.getBuildInformation(), - hosting: this.infoApi.getHostingInformation(), - sbom: this.infoApi.getSbom().pipe( - switchMap((sbomFile) => { - return sbomFile.text(); - }), - map((sbomContent) => JSON.parse(sbomContent) as Sbom), - ), - }), + this.infoApi.getSbom().pipe( + switchMap((sbomFile) => sbomFile.text()), + map((sbomContent) => JSON.parse(sbomContent) as Sbom), + ), }); } diff --git a/src/FacturXDotNet.WebEditor/src/app/core/toasts/toast.service.ts b/src/FacturXDotNet.WebEditor/src/app/core/toasts/toast.service.ts index dc9070c1..e895406e 100644 --- a/src/FacturXDotNet.WebEditor/src/app/core/toasts/toast.service.ts +++ b/src/FacturXDotNet.WebEditor/src/app/core/toasts/toast.service.ts @@ -1,6 +1,6 @@ -import { HttpErrorResponse } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable, of, Subject } from 'rxjs'; +import { getApiErrorMessage } from '../api/api-errors'; @Injectable({ providedIn: 'root', @@ -24,6 +24,14 @@ export class ToastService { } } +function getErrorMessage(error: unknown) { + if (error instanceof Error) { + return error.message; + } + + return getApiErrorMessage(error); +} + export type Toast = SuccessToast | InfoToast | ErrorToast; export type ToastInstance = Toast & { @@ -46,19 +54,3 @@ export interface InfoToast extends BaseToast { export interface ErrorToast extends BaseToast { readonly type: 'error'; } - -function getErrorMessage(err: unknown): string { - if (err instanceof Error) { - return err.message; - } - - if (err instanceof HttpErrorResponse) { - if (err.status === 0) { - return "Can't connect to server. Please check your internet connection."; - } - - return `${err.status} ${err.statusText} - ${err.message}`; - } - - return 'Unknown error'; -} diff --git a/src/FacturXDotNet.WebEditor/src/app/features/about/about-sbom.component.ts b/src/FacturXDotNet.WebEditor/src/app/features/about/about-sbom.component.ts index a1f1eaf1..9ffe6348 100644 --- a/src/FacturXDotNet.WebEditor/src/app/features/about/about-sbom.component.ts +++ b/src/FacturXDotNet.WebEditor/src/app/features/about/about-sbom.component.ts @@ -7,7 +7,7 @@ import MiniSearch from 'minisearch'; import { HighlightTextPipe } from '../../core/highlight-text/highlight-text.pipe'; import { Sbom } from '../../core/sbom'; import { Dependency, extractDependenciesFromSbom } from './dependency'; -import { downloadBlob, downloadFile } from '../../core/utils/download-blob'; +import { downloadFile } from '../../core/utils/download-blob'; import { MarkdownComponent } from 'ngx-markdown'; import { EscapeHtmlPipe } from '../../core/escape-html/escape-html.pipe'; @@ -15,7 +15,7 @@ import { EscapeHtmlPipe } from '../../core/escape-html/escape-html.pipe'; selector: 'app-about-licenses', imports: [NgTemplateOutlet, NgbTooltip, FormsModule, HighlightTextPipe, MarkdownComponent, EscapeHtmlPipe], template: ` -
    Dependencies
    +
    Dependencies
    } @else { - @if (apiConstants.value(); as apiConstants) { + @if (buildInfo.value(); as buildInfo) {

    The API server is currently in version {{ apiVersion() }} and was built on - {{ apiConstants.build.buildDate | date }}{{ buildInfo.buildDate | date }}.
    @if (apiVersionBuildMetadata(); as apiVersionBuildMetadata) { Build metadata: {{ apiVersionBuildMetadata }} }

    + } - + @if (sbom.isLoading()) { +
    + Loading... +
    + } @else if (sbom.error()) { +
    + Failed to load dependencies: +
    + {{ getApiErrorMessage(sbom.error()) }} +
    +
    + } @else { + @if (sbom.value(); as sbom) { + + } } }
@@ -190,25 +206,32 @@ export class AboutPage { protected apiUrl = inject(API_BASE_URL); private apiConstantsService = inject(ApiConstantsService); + + protected buildInfo = this.apiConstantsService.buildInfo; + protected apiVersion = computed(() => { - const apiConstants = this.apiConstantsService.info.value(); - if (apiConstants === undefined) { + const buildInfo = this.buildInfo.value(); + if (buildInfo === undefined) { return undefined; } - return removeBuildInformation(apiConstants.build.version); + return removeBuildInformation(buildInfo.version); }); protected apiVersionBuildMetadata = computed(() => { - const apiConstants = this.apiConstantsService.info.value(); - if (apiConstants === undefined) { + const buildInfo = this.buildInfo.value(); + if (buildInfo === undefined) { return undefined; } - return extractBuildInformation(apiConstants.build.version); + return extractBuildInformation(buildInfo.version); }); - protected apiConstants = this.apiConstantsService.info; + protected sbom = this.apiConstantsService.sbom; + + protected getApiErrorMessage(error: unknown) { + return getApiErrorMessage(error); + } } function extractBuildInformation(version: string) { diff --git a/src/FacturXDotNet.WebEditor/src/app/features/editor/editor.page.ts b/src/FacturXDotNet.WebEditor/src/app/features/editor/editor.page.ts index cfd39047..ea843c21 100644 --- a/src/FacturXDotNet.WebEditor/src/app/features/editor/editor.page.ts +++ b/src/FacturXDotNet.WebEditor/src/app/features/editor/editor.page.ts @@ -1,21 +1,21 @@ -import {Component, computed, effect, HostListener, inject, linkedSignal, Resource, signal, Signal} from '@angular/core'; -import {NgOptimizedImage} from '@angular/common'; -import {EditorSettings, EditorSettingsService, PdfModel} from './editor-settings.service'; -import {EditorMenuComponent} from './components/editor-menu/editor-menu.component'; -import {FormsModule} from '@angular/forms'; -import {TwoColumnsComponent} from '../../core/two-columns/two-columns.component'; -import {EditorSavedState, EditorStateService} from './editor-state.service'; -import {EditorLeftPaneHeaderComponent} from './components/editor-header/editor-left-pane-header.component'; -import {EditorWelcomeComponent} from './editor-welcome.component'; -import {API_BASE_URL} from '../../app.config'; -import {ApiServerStatusComponent} from '../../core/api/components/api-server-status.component'; -import {ApiConstantsService} from '../../core/api/services/api-constants.service'; -import {EditorMenuService} from './components/editor-menu/editor-menu.service'; -import {RouterOutlet} from '@angular/router'; -import {EditorPdfViewerComponent} from './components/editor-pdf-viewer/editor-pdf-viewer.component'; -import {EditorHeaderNameComponent} from './components/editor-header/editor-header-name.component'; -import {EditorRightPaneHeaderComponent} from './components/editor-header/editor-right-pane-header.component'; -import {EditorResponsivenessService} from './editor-responsiveness.service'; +import { Component, computed, effect, HostListener, inject, linkedSignal, Resource, signal, Signal } from '@angular/core'; +import { NgOptimizedImage } from '@angular/common'; +import { EditorSettings, EditorSettingsService, PdfModel } from './editor-settings.service'; +import { EditorMenuComponent } from './components/editor-menu/editor-menu.component'; +import { FormsModule } from '@angular/forms'; +import { TwoColumnsComponent } from '../../core/two-columns/two-columns.component'; +import { EditorSavedState, EditorStateService } from './editor-state.service'; +import { EditorLeftPaneHeaderComponent } from './components/editor-header/editor-left-pane-header.component'; +import { EditorWelcomeComponent } from './editor-welcome.component'; +import { API_BASE_URL } from '../../app.config'; +import { ApiServerStatusComponent } from '../../core/api/components/api-server-status.component'; +import { ApiConstantsService } from '../../core/api/services/api-constants.service'; +import { EditorMenuService } from './components/editor-menu/editor-menu.service'; +import { RouterOutlet } from '@angular/router'; +import { EditorPdfViewerComponent } from './components/editor-pdf-viewer/editor-pdf-viewer.component'; +import { EditorHeaderNameComponent } from './components/editor-header/editor-header-name.component'; +import { EditorRightPaneHeaderComponent } from './components/editor-header/editor-right-pane-header.component'; +import { EditorResponsivenessService } from './editor-responsiveness.service'; @Component({ selector: 'app-editor', @@ -131,8 +131,8 @@ import {EditorResponsivenessService} from './editor-responsiveness.service';
- © 2025 Ismail Bennani, made with and . The tools are open source and released under the MIT - License, feel free to use, modify, and share. + © 2025 Ismail Bennani, made with and . The tools are open source and released under the + MIT License, feel free to use, modify, and share.
@@ -160,7 +160,7 @@ export class EditorPage { }, }); private apiConstantsService = inject(ApiConstantsService); - protected unsafeEnvironment = computed(() => this.apiConstantsService.info.value()?.hosting.unsafeEnvironment ?? false); + protected unsafeEnvironment = computed(() => this.apiConstantsService.hostingInfo.value()?.unsafeEnvironment ?? false); private editorStateService = inject(EditorStateService); protected state: Resource = this.editorStateService.savedState; private editorMenuService = inject(EditorMenuService); From 59b2e32e9c3d7a40d0e1e9c0123a953e3fe30727 Mon Sep 17 00:00:00 2001 From: ismailbennani Date: Sat, 26 Apr 2025 19:40:48 +0200 Subject: [PATCH 10/25] use cyclonedx to generate license file in api CI --- .../reusable-build-publish-api-docker-image.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/reusable-build-publish-api-docker-image.yml b/.github/workflows/reusable-build-publish-api-docker-image.yml index b510045d..02e0ac98 100644 --- a/.github/workflows/reusable-build-publish-api-docker-image.yml +++ b/.github/workflows/reusable-build-publish-api-docker-image.yml @@ -48,16 +48,11 @@ jobs: with: fetch-depth: 0 - - name: Setup .NET 6 (for dotnet-project-licenses) - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 6.0.x - - name: Install dotnet-project-licenses - run: dotnet tool install -g dotnet-project-licenses --framework net6.0 + run: dotnet tool install --global CycloneDX - name: Generate licenses file - run: cd src/FacturXDotNet.API; dotnet-project-licenses -i dotnet-project-licenses-input.json --json --output-directory Resources + run: cd src/FacturXDotNet.API; dotnet-CycloneDX FacturXDotNet.API.csproj -o Resources --json -fn api.bom.json - name: Build the Docker image run: cd src; docker build . --file FacturXDotNet.API/Dockerfile --tag facturxdotnet-api --label "runnumber=${GITHUB_RUN_ID}" --build-arg VERSION=${{ inputs.version }} From 8488ff29fe4dff69ee4a02da386cb9d7757533f5 Mon Sep 17 00:00:00 2001 From: ismailbennani Date: Sat, 26 Apr 2025 19:43:18 +0200 Subject: [PATCH 11/25] cleanup --- src/FacturXDotNet.WebEditor/src/app/core/api/api.models.ts | 1 + src/FacturXDotNet.WebEditor/src/app/core/api/info.api.ts | 2 +- .../src/app/core/global-overlay/global-overlay.component.ts | 2 +- .../editor-header/editor-left-pane-header.component.ts | 2 +- .../components/editor-menu/editor-export-menu.component.ts | 2 +- .../components/editor-settings-general-form.component.ts | 2 +- ...-settings-language-pack-document-types-form.component.ts | 2 +- .../editor-settings-language-pack-form.component.ts | 2 +- .../editor-settings-pdf-profile-form.component.ts | 6 +++--- .../editor-settings-pdf-profile-create.tab.ts | 2 +- .../editor-settings-pdf-profile-edit.tab.ts | 2 +- .../src/app/features/editor/editor.layout.ts | 4 +--- .../src/app/features/editor/editor.page.ts | 1 - 13 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/FacturXDotNet.WebEditor/src/app/core/api/api.models.ts b/src/FacturXDotNet.WebEditor/src/app/core/api/api.models.ts index 588a0d4a..535bf425 100644 --- a/src/FacturXDotNet.WebEditor/src/app/core/api/api.models.ts +++ b/src/FacturXDotNet.WebEditor/src/app/core/api/api.models.ts @@ -6,6 +6,7 @@ /* tslint:disable */ /* eslint-disable */ + // ReSharper disable InconsistentNaming export class ApplicableHeaderTradeAgreement implements IApplicableHeaderTradeAgreement { diff --git a/src/FacturXDotNet.WebEditor/src/app/core/api/info.api.ts b/src/FacturXDotNet.WebEditor/src/app/core/api/info.api.ts index 18410c3c..5758c19a 100644 --- a/src/FacturXDotNet.WebEditor/src/app/core/api/info.api.ts +++ b/src/FacturXDotNet.WebEditor/src/app/core/api/info.api.ts @@ -2,7 +2,7 @@ import { inject, Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { map, Observable } from 'rxjs'; import { API_BASE_URL } from '../../app.config'; -import { IBuildInformationDto, IHostingInformationDto, IPackageDto } from './api.models'; +import { IBuildInformationDto, IHostingInformationDto } from './api.models'; @Injectable({ providedIn: 'root', diff --git a/src/FacturXDotNet.WebEditor/src/app/core/global-overlay/global-overlay.component.ts b/src/FacturXDotNet.WebEditor/src/app/core/global-overlay/global-overlay.component.ts index aa54a2f8..3008cbb9 100644 --- a/src/FacturXDotNet.WebEditor/src/app/core/global-overlay/global-overlay.component.ts +++ b/src/FacturXDotNet.WebEditor/src/app/core/global-overlay/global-overlay.component.ts @@ -1,4 +1,4 @@ -import { Component, inject, signal } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { GlobalOverlayService } from './global-overlay.service'; @Component({ diff --git a/src/FacturXDotNet.WebEditor/src/app/features/editor/components/editor-header/editor-left-pane-header.component.ts b/src/FacturXDotNet.WebEditor/src/app/features/editor/components/editor-header/editor-left-pane-header.component.ts index d5a09f9e..e61ec41e 100644 --- a/src/FacturXDotNet.WebEditor/src/app/features/editor/components/editor-header/editor-left-pane-header.component.ts +++ b/src/FacturXDotNet.WebEditor/src/app/features/editor/components/editor-header/editor-left-pane-header.component.ts @@ -6,7 +6,7 @@ import { NgbNav, NgbNavItem, NgbNavLink, NgbNavLinkBase } from '@ng-bootstrap/ng import { FormsModule } from '@angular/forms'; import { EditorResponsivenessService } from '../../services/editor-responsiveness.service'; import { toSignal } from '@angular/core/rxjs-interop'; -import { distinct, distinctUntilChanged, filter, map } from 'rxjs'; +import { distinctUntilChanged, filter, map } from 'rxjs'; import { EditorHeaderNameComponent } from './editor-header-name.component'; @Component({ diff --git a/src/FacturXDotNet.WebEditor/src/app/features/editor/components/editor-menu/editor-export-menu.component.ts b/src/FacturXDotNet.WebEditor/src/app/features/editor/components/editor-menu/editor-export-menu.component.ts index 83056b69..592b934f 100644 --- a/src/FacturXDotNet.WebEditor/src/app/features/editor/components/editor-menu/editor-export-menu.component.ts +++ b/src/FacturXDotNet.WebEditor/src/app/features/editor/components/editor-menu/editor-export-menu.component.ts @@ -1,4 +1,4 @@ -import { Component, DestroyRef, inject, output } from '@angular/core'; +import { Component, inject, output } from '@angular/core'; import { ToastService } from '../../../../core/toasts/toast.service'; import { EditorMenuService } from './editor-menu.service'; import { NgbDropdown, NgbDropdownItem, NgbDropdownMenu, NgbDropdownToggle } from '@ng-bootstrap/ng-bootstrap'; diff --git a/src/FacturXDotNet.WebEditor/src/app/features/editor/editor-tabs/editor-settings/editor-settings-tabs/editor-settings-pdf-profiles/components/editor-settings-general-form.component.ts b/src/FacturXDotNet.WebEditor/src/app/features/editor/editor-tabs/editor-settings/editor-settings-tabs/editor-settings-pdf-profiles/components/editor-settings-general-form.component.ts index f3fe0674..ac0e14e3 100644 --- a/src/FacturXDotNet.WebEditor/src/app/features/editor/editor-tabs/editor-settings/editor-settings-tabs/editor-settings-pdf-profiles/components/editor-settings-general-form.component.ts +++ b/src/FacturXDotNet.WebEditor/src/app/features/editor/editor-tabs/editor-settings/editor-settings-tabs/editor-settings-pdf-profiles/components/editor-settings-general-form.component.ts @@ -1,5 +1,5 @@ import { Component, inject, input } from '@angular/core'; -import { ControlContainer, FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { ControlContainer, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { ImportFileService } from '../../../../../../../core/import-file/import-file.service'; @Component({ diff --git a/src/FacturXDotNet.WebEditor/src/app/features/editor/editor-tabs/editor-settings/editor-settings-tabs/editor-settings-pdf-profiles/components/editor-settings-language-pack-document-types-form.component.ts b/src/FacturXDotNet.WebEditor/src/app/features/editor/editor-tabs/editor-settings/editor-settings-tabs/editor-settings-pdf-profiles/components/editor-settings-language-pack-document-types-form.component.ts index 90e1f905..e21bf444 100644 --- a/src/FacturXDotNet.WebEditor/src/app/features/editor/editor-tabs/editor-settings/editor-settings-tabs/editor-settings-pdf-profiles/components/editor-settings-language-pack-document-types-form.component.ts +++ b/src/FacturXDotNet.WebEditor/src/app/features/editor/editor-tabs/editor-settings/editor-settings-tabs/editor-settings-pdf-profiles/components/editor-settings-language-pack-document-types-form.component.ts @@ -1,5 +1,5 @@ import { Component, inject, input } from '@angular/core'; -import { ControlContainer, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { ControlContainer, ReactiveFormsModule } from '@angular/forms'; import { IStandardPdfGeneratorLanguagePackDto } from '../../../../../../../core/api/api.models'; @Component({ diff --git a/src/FacturXDotNet.WebEditor/src/app/features/editor/editor-tabs/editor-settings/editor-settings-tabs/editor-settings-pdf-profiles/components/editor-settings-language-pack-form.component.ts b/src/FacturXDotNet.WebEditor/src/app/features/editor/editor-tabs/editor-settings/editor-settings-tabs/editor-settings-pdf-profiles/components/editor-settings-language-pack-form.component.ts index 60c183e1..ec767a9a 100644 --- a/src/FacturXDotNet.WebEditor/src/app/features/editor/editor-tabs/editor-settings/editor-settings-tabs/editor-settings-pdf-profiles/components/editor-settings-language-pack-form.component.ts +++ b/src/FacturXDotNet.WebEditor/src/app/features/editor/editor-tabs/editor-settings/editor-settings-tabs/editor-settings-pdf-profiles/components/editor-settings-language-pack-form.component.ts @@ -1,5 +1,5 @@ import { Component, inject, input } from '@angular/core'; -import { ControlContainer, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { ControlContainer, ReactiveFormsModule } from '@angular/forms'; import { IStandardPdfGeneratorLanguagePackDto } from '../../../../../../../core/api/api.models'; @Component({ diff --git a/src/FacturXDotNet.WebEditor/src/app/features/editor/editor-tabs/editor-settings/editor-settings-tabs/editor-settings-pdf-profiles/components/editor-settings-pdf-profile-form.component.ts b/src/FacturXDotNet.WebEditor/src/app/features/editor/editor-tabs/editor-settings/editor-settings-tabs/editor-settings-pdf-profiles/components/editor-settings-pdf-profile-form.component.ts index f57e43c4..c120e5d5 100644 --- a/src/FacturXDotNet.WebEditor/src/app/features/editor/editor-tabs/editor-settings/editor-settings-tabs/editor-settings-pdf-profiles/components/editor-settings-pdf-profile-form.component.ts +++ b/src/FacturXDotNet.WebEditor/src/app/features/editor/editor-tabs/editor-settings/editor-settings-tabs/editor-settings-pdf-profiles/components/editor-settings-pdf-profile-form.component.ts @@ -1,8 +1,8 @@ -import { Component, computed, DestroyRef, inject, input, linkedSignal, Signal, TemplateRef, WritableSignal } from '@angular/core'; +import { Component, computed, inject, input, Signal, TemplateRef } from '@angular/core'; import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; -import { rxResource, takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; +import { rxResource, toSignal } from '@angular/core/rxjs-interop'; import { GenerateApi } from '../../../../../../../core/api/generate.api'; -import { delay, distinctUntilChanged, map, startWith } from 'rxjs'; +import { distinctUntilChanged, map } from 'rxjs'; import { IStandardPdfGeneratorLanguagePackDto } from '../../../../../../../core/api/api.models'; import { EditorPdfGenerationProfileData } from '../../../../../services/editor-pdf-generation-profiles.service'; import { EditorSettingsLanguagePackDocumentTypesFormComponent } from './editor-settings-language-pack-document-types-form.component'; diff --git a/src/FacturXDotNet.WebEditor/src/app/features/editor/editor-tabs/editor-settings/editor-settings-tabs/editor-settings-pdf-profiles/editor-settings-pdf-profile-create.tab.ts b/src/FacturXDotNet.WebEditor/src/app/features/editor/editor-tabs/editor-settings/editor-settings-tabs/editor-settings-pdf-profiles/editor-settings-pdf-profile-create.tab.ts index f43f7075..3711a1db 100644 --- a/src/FacturXDotNet.WebEditor/src/app/features/editor/editor-tabs/editor-settings/editor-settings-tabs/editor-settings-pdf-profiles/editor-settings-pdf-profile-create.tab.ts +++ b/src/FacturXDotNet.WebEditor/src/app/features/editor/editor-tabs/editor-settings/editor-settings-tabs/editor-settings-pdf-profiles/editor-settings-pdf-profile-create.tab.ts @@ -1,7 +1,7 @@ import { Component, inject, viewChild } from '@angular/core'; import { Router, RouterLink } from '@angular/router'; import { EditorSettingsPdfProfileFormComponent } from './components/editor-settings-pdf-profile-form.component'; -import { EditorPdfGenerationProfileData, EditorPdfGenerationProfilesService } from '../../../../services/editor-pdf-generation-profiles.service'; +import { EditorPdfGenerationProfilesService } from '../../../../services/editor-pdf-generation-profiles.service'; import { ToastService } from '../../../../../../core/toasts/toast.service'; import { EditorPdfViewerService } from '../../../editor-pdf-viewer/editor-pdf-viewer.service'; diff --git a/src/FacturXDotNet.WebEditor/src/app/features/editor/editor-tabs/editor-settings/editor-settings-tabs/editor-settings-pdf-profiles/editor-settings-pdf-profile-edit.tab.ts b/src/FacturXDotNet.WebEditor/src/app/features/editor/editor-tabs/editor-settings/editor-settings-tabs/editor-settings-pdf-profiles/editor-settings-pdf-profile-edit.tab.ts index 81240b5a..f41fbb1d 100644 --- a/src/FacturXDotNet.WebEditor/src/app/features/editor/editor-tabs/editor-settings/editor-settings-tabs/editor-settings-pdf-profiles/editor-settings-pdf-profile-edit.tab.ts +++ b/src/FacturXDotNet.WebEditor/src/app/features/editor/editor-tabs/editor-settings/editor-settings-tabs/editor-settings-pdf-profiles/editor-settings-pdf-profile-edit.tab.ts @@ -1,7 +1,7 @@ import { Component, computed, effect, inject, input, viewChild } from '@angular/core'; import { EditorSettingsPdfProfileFormComponent } from './components/editor-settings-pdf-profile-form.component'; import { Router, RouterLink } from '@angular/router'; -import { EditorPdfGenerationProfile, EditorPdfGenerationProfileData, EditorPdfGenerationProfilesService } from '../../../../services/editor-pdf-generation-profiles.service'; +import { EditorPdfGenerationProfilesService } from '../../../../services/editor-pdf-generation-profiles.service'; import { ToastService } from '../../../../../../core/toasts/toast.service'; import { EditorPdfViewerService } from '../../../editor-pdf-viewer/editor-pdf-viewer.service'; import { NgbDropdown, NgbDropdownItem, NgbDropdownMenu, NgbDropdownToggle } from '@ng-bootstrap/ng-bootstrap'; diff --git a/src/FacturXDotNet.WebEditor/src/app/features/editor/editor.layout.ts b/src/FacturXDotNet.WebEditor/src/app/features/editor/editor.layout.ts index bb73ff07..71ac2c68 100644 --- a/src/FacturXDotNet.WebEditor/src/app/features/editor/editor.layout.ts +++ b/src/FacturXDotNet.WebEditor/src/app/features/editor/editor.layout.ts @@ -1,9 +1,7 @@ -import { Component, computed, effect, HostListener, inject, linkedSignal, Resource, signal, Signal } from '@angular/core'; +import { Component, computed, effect, inject } from '@angular/core'; import { NgOptimizedImage } from '@angular/common'; -import { EditorSettings, EditorSettingsService, PdfModel } from './services/editor-settings.service'; import { EditorMenuComponent } from './components/editor-menu/editor-menu.component'; import { FormsModule } from '@angular/forms'; -import { EditorSavedState, EditorStateService } from './services/editor-state.service'; import { API_BASE_URL } from '../../app.config'; import { ApiServerStatusComponent } from '../../core/api/components/api-server-status.component'; import { ApiConstantsService } from '../../core/api/services/api-constants.service'; diff --git a/src/FacturXDotNet.WebEditor/src/app/features/editor/editor.page.ts b/src/FacturXDotNet.WebEditor/src/app/features/editor/editor.page.ts index d9ce5598..7a8bf06b 100644 --- a/src/FacturXDotNet.WebEditor/src/app/features/editor/editor.page.ts +++ b/src/FacturXDotNet.WebEditor/src/app/features/editor/editor.page.ts @@ -2,7 +2,6 @@ import { Component, computed, effect, HostListener, inject, linkedSignal, Resour import { EditorLeftPaneHeaderComponent } from './components/editor-header/editor-left-pane-header.component'; import { EditorPdfViewerComponent } from './editor-tabs/editor-pdf-viewer/editor-pdf-viewer.component'; import { EditorRightPaneHeaderComponent } from './components/editor-header/editor-right-pane-header.component'; -import { EditorWelcomePage } from './editor-welcome.page'; import { Router, RouterOutlet } from '@angular/router'; import { TwoColumnsComponent } from '../../core/two-columns/two-columns.component'; import { EditorSavedState, EditorStateService } from './services/editor-state.service'; From 9fbfeac1efa74a6361ccebbe0b35a5f9547d2af4 Mon Sep 17 00:00:00 2001 From: ismailbennani Date: Sat, 26 Apr 2025 19:44:38 +0200 Subject: [PATCH 12/25] fix build --- .../src/app/features/editor/editor.layout.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FacturXDotNet.WebEditor/src/app/features/editor/editor.layout.ts b/src/FacturXDotNet.WebEditor/src/app/features/editor/editor.layout.ts index 71ac2c68..3ef93e7e 100644 --- a/src/FacturXDotNet.WebEditor/src/app/features/editor/editor.layout.ts +++ b/src/FacturXDotNet.WebEditor/src/app/features/editor/editor.layout.ts @@ -66,7 +66,7 @@ export class EditorLayout { protected apiUrl = inject(API_BASE_URL); private apiConstantsService = inject(ApiConstantsService); - protected unsafeEnvironment = computed(() => this.apiConstantsService.info.value()?.hosting.unsafeEnvironment ?? false); + protected unsafeEnvironment = computed(() => this.apiConstantsService.hostingInfo.value()?.unsafeEnvironment ?? false); private globalOverlayService = inject(GlobalOverlayService); private editorMenuService = inject(EditorMenuService); From 02f2a118bf2f9c0b6b694507a931a28c2c92d9c1 Mon Sep 17 00:00:00 2001 From: ismailbennani Date: Sat, 26 Apr 2025 19:45:42 +0200 Subject: [PATCH 13/25] fix CI --- .github/workflows/reusable-build-publish-api-docker-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-build-publish-api-docker-image.yml b/.github/workflows/reusable-build-publish-api-docker-image.yml index 02e0ac98..0ed59d9d 100644 --- a/.github/workflows/reusable-build-publish-api-docker-image.yml +++ b/.github/workflows/reusable-build-publish-api-docker-image.yml @@ -49,7 +49,7 @@ jobs: fetch-depth: 0 - name: Install dotnet-project-licenses - run: dotnet tool install --global CycloneDX + run: dotnet tool install --global CycloneDX --framework net8.0 - name: Generate licenses file run: cd src/FacturXDotNet.API; dotnet-CycloneDX FacturXDotNet.API.csproj -o Resources --json -fn api.bom.json From 250bc308b251dbec295e5e04b028f50d22b2fa13 Mon Sep 17 00:00:00 2001 From: ismailbennani Date: Sat, 26 Apr 2025 20:01:08 +0200 Subject: [PATCH 14/25] generate correct sbom files for docs --- .../reusable-build-docs-application.yml | 61 +++++-------------- ...eusable-build-publish-api-docker-image.yml | 4 +- 2 files changed, 16 insertions(+), 49 deletions(-) diff --git a/.github/workflows/reusable-build-docs-application.yml b/.github/workflows/reusable-build-docs-application.yml index af841d0f..8c5c90ee 100644 --- a/.github/workflows/reusable-build-docs-application.yml +++ b/.github/workflows/reusable-build-docs-application.yml @@ -70,8 +70,8 @@ jobs: - name: Install dfmg run: dotnet tool install -g DocFxMarkdownGen - - name: Install dotnet-project-licenses - run: dotnet tool install -g dotnet-project-licenses + - name: Install CycloneDX + run: dotnet tool install --global CycloneDX --framework net8.0 - name: Generate .NET API reference run: | @@ -155,53 +155,20 @@ jobs: - name: Install dependencies run: cd docs; npm ci - - name: Install dependencies in editor (for license-report) - run: cd src/FacturXDotNet.WebEditor; npm ci + - name: Generate Editor SBOM + run: cd src/FacturXDotNet.WebEditor; npm sbom --sbom-format cyclonedx > ../../docs/src/assets/editor.sbom.json - - name: Install license-report - run: npm i -g license-report + - name: Generate Docs SBOM + run: cd docs; npm sbom --sbom-format cyclonedx > src/assets/docs.sbom.json - - name: Generate licenses - run: | - license-report --config docs/license-report-config.json --package src/FacturXDotNet.WebEditor/package.json > docs/src/assets/editor-licenses.json - - echo - echo docs/src/assets/editor-licenses.json - cat docs/src/assets/editor-licenses.json - echo - echo - - license-report --config docs/license-report-config.json --package docs/package.json > docs/src/assets/docs-licenses.json - - echo - echo docs/src/assets/docs-licenses.json - cat docs/src/assets/docs-licenses.json - echo - echo - - dotnet-project-licenses -i src/FacturXDotNet/FacturXDotNet.csproj --json --output-directory docs/src/assets --outfile library-licenses.json - - echo - echo docs/src/assets/library-licenses.json - cat docs/src/assets/library-licenses.json - echo - echo - - dotnet-project-licenses -i src/FacturXDotNet.API/FacturXDotNet.API.csproj --json --output-directory docs/src/assets --outfile api-licenses.json - - echo - echo docs/src/assets/api-licenses.json - cat docs/src/assets/api-licenses.json - echo - echo - - dotnet-project-licenses -i src/FacturXDotNet.CLI/FacturXDotNet.CLI.csproj --json --output-directory docs/src/assets --outfile cli-licenses.json - - echo - echo docs/src/assets/cli-licenses.json - cat docs/src/assets/cli-licenses.json - echo - echo + - name: Generate API SBOM + run: cd src/FacturXDotNet.API dotnet-CycloneDX FacturXDotNet.API.csproj -o ../../docs/src/assets --json -fn api.bom.json + + - name: Generate CLI SBOM + run: cd src/FacturXDotNet.CLI dotnet-CycloneDX FacturXDotNet.API.csproj -o ../../docs/src/assets --json -fn cli.bom.json + + - name: Generate Library SBOM + run: cd src/FacturXDotNet dotnet-CycloneDX FacturXDotNet.API.csproj -o ../../docs/src/assets --json -fn library.bom.json - name: Write env.json run: | diff --git a/.github/workflows/reusable-build-publish-api-docker-image.yml b/.github/workflows/reusable-build-publish-api-docker-image.yml index 0ed59d9d..eeb725fe 100644 --- a/.github/workflows/reusable-build-publish-api-docker-image.yml +++ b/.github/workflows/reusable-build-publish-api-docker-image.yml @@ -48,7 +48,7 @@ jobs: with: fetch-depth: 0 - - name: Install dotnet-project-licenses + - name: Install CycloneDX run: dotnet tool install --global CycloneDX --framework net8.0 - name: Generate licenses file @@ -83,7 +83,7 @@ jobs: package-name: facturxdotnet-api tag: ${{ inputs.tag }} github-token: ${{ secrets.GITHUB_TOKEN }} - + - name: Push ${{ inputs.tag }} if: inputs.tag != '' run: | From e9718f32f324bcff4930633807908944e2ad1b11 Mon Sep 17 00:00:00 2001 From: ismailbennani Date: Sat, 26 Apr 2025 20:06:12 +0200 Subject: [PATCH 15/25] fix workflow --- .github/workflows/reusable-build-docs-application.yml | 9 ++------- .../reusable-build-publish-api-docker-image.yml | 5 +++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/reusable-build-docs-application.yml b/.github/workflows/reusable-build-docs-application.yml index 8c5c90ee..5af65704 100644 --- a/.github/workflows/reusable-build-docs-application.yml +++ b/.github/workflows/reusable-build-docs-application.yml @@ -38,16 +38,11 @@ jobs: with: fetch-depth: 0 - - name: Setup .NET 9 (for lib and api) + - name: Setup .NET 9 (for CycloneDX, lib and api) uses: actions/setup-dotnet@v4 with: dotnet-version: 9.0.x - - name: Setup .NET 7 (for dotnet-project-licenses) - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 7.0.x - - name: Setup .NET 6 (for dfmg) uses: actions/setup-dotnet@v4 with: @@ -176,7 +171,7 @@ jobs: echo ' "buildName": "${{ inputs.build-name }}",' >> docs/src/env.json echo ' "version": "${{ inputs.version }}",' >> docs/src/env.json echo ' "editor": {' >> docs/src/env.json - echo ' "url": "${{ inputs.editor-url }}"' >> docs/src/env.json + echo ' "url": "${{ inputs.editor-url }}"' >> docs/src/env.json echo ' },' >> docs/src/env.json echo ' "api": {' >> docs/src/env.json echo ' "url": "${{ inputs.api-url }}"' >> docs/src/env.json diff --git a/.github/workflows/reusable-build-publish-api-docker-image.yml b/.github/workflows/reusable-build-publish-api-docker-image.yml index eeb725fe..10326c65 100644 --- a/.github/workflows/reusable-build-publish-api-docker-image.yml +++ b/.github/workflows/reusable-build-publish-api-docker-image.yml @@ -48,6 +48,11 @@ jobs: with: fetch-depth: 0 + - name: Setup .NET 9 (for CycloneDX) + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 9.0.x + - name: Install CycloneDX run: dotnet tool install --global CycloneDX --framework net8.0 From 2ef3ca8b33c85f3fa80a39d9b5bda74c6d5c01d9 Mon Sep 17 00:00:00 2001 From: ismailbennani Date: Sat, 26 Apr 2025 20:07:20 +0200 Subject: [PATCH 16/25] fix workflow --- .github/workflows/reusable-build-docs-application.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/reusable-build-docs-application.yml b/.github/workflows/reusable-build-docs-application.yml index 5af65704..273e8f99 100644 --- a/.github/workflows/reusable-build-docs-application.yml +++ b/.github/workflows/reusable-build-docs-application.yml @@ -150,6 +150,9 @@ jobs: - name: Install dependencies run: cd docs; npm ci + - name: Install dependencies in editor (for sbom generation) + run: cd src/FacturXDotNet.WebEditor; npm ci + - name: Generate Editor SBOM run: cd src/FacturXDotNet.WebEditor; npm sbom --sbom-format cyclonedx > ../../docs/src/assets/editor.sbom.json From 7c1b26ba6aec67e7e4b259d6b92896611f0caaab Mon Sep 17 00:00:00 2001 From: ismailbennani Date: Sat, 26 Apr 2025 20:30:59 +0200 Subject: [PATCH 17/25] use sbom in docs --- .../reusable-build-docs-application.yml | 10 +- docs/.gitignore | 10 +- docs/license-report-config.json | 5 - docs/package.json | 2 + docs/src/dependencies.data.ts | 252 +++++++++++------- .../.idea.FacturXDotNet/.idea/encodings.xml | 7 + 6 files changed, 179 insertions(+), 107 deletions(-) delete mode 100644 docs/license-report-config.json create mode 100644 src/.idea/.idea.FacturXDotNet/.idea/encodings.xml diff --git a/.github/workflows/reusable-build-docs-application.yml b/.github/workflows/reusable-build-docs-application.yml index 273e8f99..eb82d0cb 100644 --- a/.github/workflows/reusable-build-docs-application.yml +++ b/.github/workflows/reusable-build-docs-application.yml @@ -154,19 +154,19 @@ jobs: run: cd src/FacturXDotNet.WebEditor; npm ci - name: Generate Editor SBOM - run: cd src/FacturXDotNet.WebEditor; npm sbom --sbom-format cyclonedx > ../../docs/src/assets/editor.sbom.json + run: cd src/FacturXDotNet.WebEditor; npm sbom --sbom-format cyclonedx > ../../docs/src/assets/editor.bom.json - name: Generate Docs SBOM - run: cd docs; npm sbom --sbom-format cyclonedx > src/assets/docs.sbom.json + run: cd docs; npm sbom --sbom-format cyclonedx > src/assets/docs.bom.json - name: Generate API SBOM - run: cd src/FacturXDotNet.API dotnet-CycloneDX FacturXDotNet.API.csproj -o ../../docs/src/assets --json -fn api.bom.json + run: cd src/FacturXDotNet.API; dotnet-CycloneDX FacturXDotNet.API.csproj -o ../../docs/src/assets --json -fn api.bom.json - name: Generate CLI SBOM - run: cd src/FacturXDotNet.CLI dotnet-CycloneDX FacturXDotNet.API.csproj -o ../../docs/src/assets --json -fn cli.bom.json + run: cd src/FacturXDotNet.CLI; dotnet-CycloneDX FacturXDotNet.CLI.csproj -o ../../docs/src/assets --json -fn cli.bom.json - name: Generate Library SBOM - run: cd src/FacturXDotNet dotnet-CycloneDX FacturXDotNet.API.csproj -o ../../docs/src/assets --json -fn library.bom.json + run: cd src/FacturXDotNet; dotnet-CycloneDX FacturXDotNet.csproj -o ../../docs/src/assets --json -fn library.bom.json - name: Write env.json run: | diff --git a/docs/.gitignore b/docs/.gitignore index 68157175..45286a4a 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -62,8 +62,8 @@ TODOs.md src/api-reference/** src/cli/** src/assets/facturxdotnet.openapi.json -src/assets/docs-licenses.json -src/assets/editor-licenses.json -src/assets/api-licenses.json -src/assets/cli-licenses.json -src/assets/library-licenses.json +src/assets/docs.bom.json +src/assets/editor.bom.json +src/assets/api.bom.json +src/assets/cli.bom.json +src/assets/library.bom.json diff --git a/docs/license-report-config.json b/docs/license-report-config.json deleted file mode 100644 index b4ccc78c..00000000 --- a/docs/license-report-config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "output": "json", - "only": "prod,dev,opt,peer", - "fields": ["name", "author", "installedVersion", "licenseType", "link"] -} diff --git a/docs/package.json b/docs/package.json index d26ecc87..2a38078d 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,4 +1,6 @@ { + "name": "facturxdotnet-docs", + "version": "0.0.0.0", "type": "module", "scripts": { "dev": "vitepress dev --port 40850", diff --git a/docs/src/dependencies.data.ts b/docs/src/dependencies.data.ts index 6c59f222..58deef47 100644 --- a/docs/src/dependencies.data.ts +++ b/docs/src/dependencies.data.ts @@ -1,57 +1,75 @@ import * as fs from "node:fs"; export default { + watch: [ + "src/assets/docs.bom.json", + "src/assets/editor.bom.json", + "src/assets/api.bom.json", + "src/assets/cli.bom.json", + "src/assets/library.bom.json", + ], load(): Dependencies { - return { - docs: groupDependenciesByLicense([ - ...loadDependenciesFromLicenseReportOutput( - "src/assets/docs-licenses.json", + const docsSbom = loadSbom("src/assets/docs.bom.json"); + const editorSbom = loadSbom("src/assets/editor.bom.json"); + const apiSbom = loadSbom("src/assets/api.bom.json"); + const cliSbom = loadSbom("src/assets/cli.bom.json"); + const librarySbom = loadSbom("src/assets/library.bom.json"); + + const result: Dependencies = { + docs: { + sbom: docsSbom, + licenses: groupDependenciesByLicense([ + ...loadDependenciesFromSbom(docsSbom), + { + name: "docfx", + author: ".NET Foundation and Contributors", + version: "2.78.3", + license: "MIT", + link: "https://github.com/dotnet/docfx", + }, + { + name: "DocFxMarkdownGen ", + author: "Jan0660 ", + version: "0.4.2", + license: "MIT", + link: "https://github.com/Jan0660/DocFxMarkdownGen", + }, + ]), + }, + editor: { + sbom: editorSbom, + licenses: groupDependenciesByLicense( + loadDependenciesFromSbom(editorSbom), ), - { - name: "docfx", - author: ".NET Foundation and Contributors", - version: "2.78.3", - license: "MIT", - link: "https://github.com/dotnet/docfx", - }, - { - name: "DocFxMarkdownGen ", - author: "Jan0660 ", - version: "0.4.2", - license: "MIT", - link: "https://github.com/Jan0660/DocFxMarkdownGen", - }, - ]), - editor: groupDependenciesByLicense( - loadDependenciesFromLicenseReportOutput( - "src/assets/editor-licenses.json", + }, + api: { + sbom: apiSbom, + licenses: groupDependenciesByLicense(loadDependenciesFromSbom(apiSbom)), + }, + cli: { + sbom: cliSbom, + licenses: groupDependenciesByLicense(loadDependenciesFromSbom(cliSbom)), + }, + library: { + sbom: librarySbom, + licenses: groupDependenciesByLicense( + loadDependenciesFromSbom(librarySbom), ), - ), - api: groupDependenciesByLicense( - loadDependenciesFromDotNetProjectLicensesOutput( - "src/assets/api-licenses.json", - ), - ), - cli: groupDependenciesByLicense( - loadDependenciesFromDotNetProjectLicensesOutput( - "src/assets/cli-licenses.json", - ), - ), - library: groupDependenciesByLicense( - loadDependenciesFromDotNetProjectLicensesOutput( - "src/assets/library-licenses.json", - ), - ), + }, }; + + console.log(result); + + return result; }, }; interface Dependencies { - docs: LicenseGroup[]; - editor: LicenseGroup[]; - api: LicenseGroup[]; - cli: LicenseGroup[]; - library: LicenseGroup[]; + docs: { sbom: Sbom; licenses: LicenseGroup[] }; + editor: { sbom: Sbom; licenses: LicenseGroup[] }; + api: { sbom: Sbom; licenses: LicenseGroup[] }; + cli: { sbom: Sbom; licenses: LicenseGroup[] }; + library: { sbom: Sbom; licenses: LicenseGroup[] }; } interface LicenseGroup { @@ -88,66 +106,73 @@ function groupDependenciesByLicense( ); } -function loadDependenciesFromLicenseReportOutput(path: string): Dependency[] { +function loadSbom(path: string) { const fileContent = fs.readFileSync(path, "utf8"); - const parsed = JSON.parse(fileContent) as LicenseReportOutput; - return parsed.map( - (d: LicenseReportOutputElement): Dependency => ({ - name: d.name, - author: d.author, - version: d.installedVersion, - license: d.licenseType, - link: getRepositoryUrl(d.link), - }), - ); + return JSON.parse(fileContent) as Sbom; } -type LicenseReportOutput = LicenseReportOutputElement[]; +function loadDependenciesFromSbom(sbom: Sbom): Dependency[] { + const thisComponentName = sbom.metadata.component["bom-ref"]; + const thisComponentDependencies = sbom.dependencies[thisComponentName]; + if (thisComponentDependencies === undefined) { + return []; + } -interface LicenseReportOutputElement { - readonly name: string; - readonly author: string; - readonly installedVersion: string; - readonly licenseType: string; - readonly link: string; -} + const dependencies = sbom.components.filter(c => + thisComponentDependencies.includes(c["bom-ref"]), + ); -function loadDependenciesFromDotNetProjectLicensesOutput( - path: string, -): Dependency[] { - const fileContent = fs.readFileSync(path, "utf8"); - const parsed = JSON.parse(fileContent) as DotNetProjectLicensesOutput; - return parsed.map( - (d: DotNetProjectLicensesOutputElement): Dependency => ({ - name: d.PackageName, - author: d.Authors.join(", "), - version: d.PackageVersion, - license: d.LicenseType, - link: getRepositoryUrl(d.Repository?.Url), + return dependencies.map( + (component: SbomComponent): Dependency => ({ + name: component.name, + author: component.author, + version: component.version, + license: getLicense(component.licenses), + link: getLink(component.externalReferences), }), ); } -type DotNetProjectLicensesOutput = DotNetProjectLicensesOutputElement[]; - -interface DotNetProjectLicensesOutputElement { - readonly PackageName: string; - readonly PackageVersion: string; - readonly PackageUrl: string; - readonly Copyright: string; - readonly Authors: string[]; - readonly Description: string; - readonly LicenseUrl: string; - readonly LicenseType: string; - readonly Repository: { - readonly Type: string; - readonly Url: string; - readonly Commit: string; - }; +function getLicense(licenses: SbomLicense[] | undefined): string | undefined { + if (licenses === undefined) { + return undefined; + } + + const licenseNames = licenses.map(license => { + if (isSbomLicenseExpression(license)) { + if ( + license.expression.startsWith("(") && + license.expression.endsWith(")") + ) { + return license.expression.substring(1, license.expression.length - 2); + } + + return license.expression; + } + + return license.license.id; + }); + + return licenseNames.join(" OR "); } -const gitPlusUrlRegExp = new RegExp(/git\+(.*)\.git/); -const gitSchemeUrlRegExp = new RegExp(/git:\/\/(.*)\.git/); +function getLink( + externalReferences: SbomExternalReference[] | undefined, +): string | undefined { + if (externalReferences === undefined) { + return undefined; + } + + const vcsLink = externalReferences.find(r => r.type === "vcs")?.url; + if (vcsLink !== undefined) { + return getRepositoryUrl(vcsLink); + } + + return externalReferences.find(r => r.type === "website")?.url; +} + +const gitPlusUrlRegExp = new RegExp(/git\+(.*)\.git/g); +const gitSchemeUrlRegExp = new RegExp(/git:\/\/(.*)\.git/g); function getRepositoryUrl(url: string): string { if (url === undefined || url === null) { @@ -166,3 +191,46 @@ function getRepositoryUrl(url: string): string { return url; } + +export interface Sbom { + readonly version: number; + readonly metadata: { + readonly component: SbomComponent; + }; + readonly components: SbomComponent[]; + readonly dependencies: { + readonly ref: string; + readonly dependsOn: string[]; + }[]; +} + +export interface SbomComponent { + readonly "bom-ref": string; + readonly name: string; + readonly version: string; + readonly author?: string; + readonly description?: string; + readonly licenses?: SbomLicense[]; + readonly externalReferences: SbomExternalReference[]; +} + +export type SbomLicense = SbomLicenseId | SbomLicenseExpression; + +export interface SbomLicenseId { + readonly license: { readonly id: string }; +} + +export interface SbomLicenseExpression { + readonly expression: string; +} + +export function isSbomLicenseExpression( + license: SbomLicense, +): license is SbomLicenseExpression { + return Object.keys(license).includes("expression"); +} + +export interface SbomExternalReference { + readonly type: string; + readonly url: string; +} diff --git a/src/.idea/.idea.FacturXDotNet/.idea/encodings.xml b/src/.idea/.idea.FacturXDotNet/.idea/encodings.xml new file mode 100644 index 00000000..ee5b142b --- /dev/null +++ b/src/.idea/.idea.FacturXDotNet/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file From 3945bfba118217efc3baafdbb3515b51a9345306 Mon Sep 17 00:00:00 2001 From: ismailbennani Date: Sat, 26 Apr 2025 20:40:31 +0200 Subject: [PATCH 18/25] move sbom to public assets --- docs/.gitignore | 10 ++--- docs/.vitepress/config.mts | 6 +-- docs/src/dependencies.data.ts | 38 +++++++++---------- docs/src/guides/about.md | 1 - .../.idea.FacturXDotNet/.idea/encodings.xml | 4 +- 5 files changed, 29 insertions(+), 30 deletions(-) diff --git a/docs/.gitignore b/docs/.gitignore index 45286a4a..8c4a341b 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -62,8 +62,8 @@ TODOs.md src/api-reference/** src/cli/** src/assets/facturxdotnet.openapi.json -src/assets/docs.bom.json -src/assets/editor.bom.json -src/assets/api.bom.json -src/assets/cli.bom.json -src/assets/library.bom.json +src/public/docs.bom.json +src/public/editor.bom.json +src/public/api.bom.json +src/public/cli.bom.json +src/public/library.bom.json diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 034ed53b..fd40865e 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -25,7 +25,7 @@ let cliItems = collapsed: false, collapsible: false, })[0]?.items ?? []; -cliItems = cliItems.map((item) => ({ +cliItems = cliItems.map(item => ({ ...item, text: item.text.toLowerCase() === "subcommands" ? "Sub Commands" : item.text, })); @@ -39,7 +39,7 @@ let apiReferenceItems = collapsible: true, })[0]?.items ?? []; apiReferenceItems = apiReferenceItems.filter( - (i) => i.items !== undefined && i.items.length > 0, + i => i.items !== undefined && i.items.length > 0, ); const semVersion = semver.valid(env.version) @@ -218,7 +218,7 @@ export default defineConfigWithTheme({ }, }, - transformPageData: (pageData) => { + transformPageData: pageData => { const result = { ...pageData }; result.frontmatter = expandEnvInRecord(pageData.frontmatter); return result; diff --git a/docs/src/dependencies.data.ts b/docs/src/dependencies.data.ts index 58deef47..aadb9614 100644 --- a/docs/src/dependencies.data.ts +++ b/docs/src/dependencies.data.ts @@ -9,15 +9,15 @@ export default { "src/assets/library.bom.json", ], load(): Dependencies { - const docsSbom = loadSbom("src/assets/docs.bom.json"); - const editorSbom = loadSbom("src/assets/editor.bom.json"); - const apiSbom = loadSbom("src/assets/api.bom.json"); - const cliSbom = loadSbom("src/assets/cli.bom.json"); - const librarySbom = loadSbom("src/assets/library.bom.json"); + const docsSbom = loadSbom("src/public/docs.bom.json"); + const editorSbom = loadSbom("src/public/editor.bom.json"); + const apiSbom = loadSbom("src/public/api.bom.json"); + const cliSbom = loadSbom("src/public/cli.bom.json"); + const librarySbom = loadSbom("src/public/library.bom.json"); const result: Dependencies = { docs: { - sbom: docsSbom, + sbomLink: "/docs.bom.json", licenses: groupDependenciesByLicense([ ...loadDependenciesFromSbom(docsSbom), { @@ -37,21 +37,21 @@ export default { ]), }, editor: { - sbom: editorSbom, + sbomLink: "/editor.bom.json", licenses: groupDependenciesByLicense( loadDependenciesFromSbom(editorSbom), ), }, api: { - sbom: apiSbom, + sbomLink: "/api.bom.json", licenses: groupDependenciesByLicense(loadDependenciesFromSbom(apiSbom)), }, cli: { - sbom: cliSbom, + sbomLink: "/cli.bom.json", licenses: groupDependenciesByLicense(loadDependenciesFromSbom(cliSbom)), }, library: { - sbom: librarySbom, + sbomLink: "/library.bom.json", licenses: groupDependenciesByLicense( loadDependenciesFromSbom(librarySbom), ), @@ -65,11 +65,11 @@ export default { }; interface Dependencies { - docs: { sbom: Sbom; licenses: LicenseGroup[] }; - editor: { sbom: Sbom; licenses: LicenseGroup[] }; - api: { sbom: Sbom; licenses: LicenseGroup[] }; - cli: { sbom: Sbom; licenses: LicenseGroup[] }; - library: { sbom: Sbom; licenses: LicenseGroup[] }; + docs: { sbomLink: string; licenses: LicenseGroup[] }; + editor: { sbomLink: string; licenses: LicenseGroup[] }; + api: { sbomLink: string; licenses: LicenseGroup[] }; + cli: { sbomLink: string; licenses: LicenseGroup[] }; + library: { sbomLink: string; licenses: LicenseGroup[] }; } interface LicenseGroup { @@ -118,7 +118,7 @@ function loadDependenciesFromSbom(sbom: Sbom): Dependency[] { return []; } - const dependencies = sbom.components.filter(c => + const dependencies = sbom.components.filter((c) => thisComponentDependencies.includes(c["bom-ref"]), ); @@ -138,7 +138,7 @@ function getLicense(licenses: SbomLicense[] | undefined): string | undefined { return undefined; } - const licenseNames = licenses.map(license => { + const licenseNames = licenses.map((license) => { if (isSbomLicenseExpression(license)) { if ( license.expression.startsWith("(") && @@ -163,12 +163,12 @@ function getLink( return undefined; } - const vcsLink = externalReferences.find(r => r.type === "vcs")?.url; + const vcsLink = externalReferences.find((r) => r.type === "vcs")?.url; if (vcsLink !== undefined) { return getRepositoryUrl(vcsLink); } - return externalReferences.find(r => r.type === "website")?.url; + return externalReferences.find((r) => r.type === "website")?.url; } const gitPlusUrlRegExp = new RegExp(/git\+(.*)\.git/g); diff --git a/docs/src/guides/about.md b/docs/src/guides/about.md index 27edfbb9..e22ac5cf 100644 --- a/docs/src/guides/about.md +++ b/docs/src/guides/about.md @@ -48,7 +48,6 @@ To ensure transparency and maintainability, the dependencies used across the pro Here’s a breakdown of the dependencies for each part of the project: - ### Documentation website (this website) diff --git a/src/.idea/.idea.FacturXDotNet/.idea/encodings.xml b/src/.idea/.idea.FacturXDotNet/.idea/encodings.xml index ee5b142b..7261df15 100644 --- a/src/.idea/.idea.FacturXDotNet/.idea/encodings.xml +++ b/src/.idea/.idea.FacturXDotNet/.idea/encodings.xml @@ -1,7 +1,7 @@ - - + + \ No newline at end of file From dbf0e2438a97e146d24828029f02b3e910828b16 Mon Sep 17 00:00:00 2001 From: ismailbennani Date: Sat, 26 Apr 2025 20:41:46 +0200 Subject: [PATCH 19/25] minor --- .github/workflows/reusable-build-docs-application.yml | 2 +- .github/workflows/reusable-build-publish-api-docker-image.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/reusable-build-docs-application.yml b/.github/workflows/reusable-build-docs-application.yml index eb82d0cb..b454fcd4 100644 --- a/.github/workflows/reusable-build-docs-application.yml +++ b/.github/workflows/reusable-build-docs-application.yml @@ -66,7 +66,7 @@ jobs: run: dotnet tool install -g DocFxMarkdownGen - name: Install CycloneDX - run: dotnet tool install --global CycloneDX --framework net8.0 + run: dotnet tool install --global CycloneDX - name: Generate .NET API reference run: | diff --git a/.github/workflows/reusable-build-publish-api-docker-image.yml b/.github/workflows/reusable-build-publish-api-docker-image.yml index 10326c65..c01334eb 100644 --- a/.github/workflows/reusable-build-publish-api-docker-image.yml +++ b/.github/workflows/reusable-build-publish-api-docker-image.yml @@ -54,7 +54,7 @@ jobs: dotnet-version: 9.0.x - name: Install CycloneDX - run: dotnet tool install --global CycloneDX --framework net8.0 + run: dotnet tool install --global CycloneDX - name: Generate licenses file run: cd src/FacturXDotNet.API; dotnet-CycloneDX FacturXDotNet.API.csproj -o Resources --json -fn api.bom.json From ee9f318817216ad15aa2649d8121dfaaca5537e9 Mon Sep 17 00:00:00 2001 From: ismailbennani Date: Sat, 26 Apr 2025 22:17:45 +0200 Subject: [PATCH 20/25] use sbom in docs --- docs/.vitepress/theme/custom.css | 5 + docs/package-lock.json | 59 ++++++++- docs/package.json | 4 +- docs/src/components/Licenses.vue | 21 ---- docs/src/dependencies.data.ts | 126 +++++++++++++------- docs/src/guides/about.md | 24 ++-- docs/src/guides/components/Dependencies.vue | 68 +++++++++++ 7 files changed, 226 insertions(+), 81 deletions(-) delete mode 100644 docs/src/components/Licenses.vue create mode 100644 docs/src/guides/components/Dependencies.vue diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css index 110b3b18..1e802143 100644 --- a/docs/.vitepress/theme/custom.css +++ b/docs/.vitepress/theme/custom.css @@ -103,3 +103,8 @@ p:has(.sidebar-footer) { --vp-sidebar-width: 472px; /* default: 272px */ } } + +.dependency-description p { + margin-top: 0; + margin-bottom: 0; +} \ No newline at end of file diff --git a/docs/package-lock.json b/docs/package-lock.json index cf529657..cfc0cc6e 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -1,10 +1,14 @@ { - "name": "docs", + "name": "facturxdotnet-docs", + "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { + "name": "facturxdotnet-docs", + "version": "0.0.0", "dependencies": { + "markdown-it": "^14.1.0", "semver": "^7.7.1", "vitepress-openapi": "^0.0.3-alpha.76", "vitepress-plugin-auto-sidebar": "^1.3.5", @@ -2228,6 +2232,15 @@ "node": ">=18" } }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", @@ -2275,6 +2288,29 @@ "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", "license": "MIT" }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, "node_modules/mdast-util-to-hast": { "version": "13.2.0", "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", @@ -2296,6 +2332,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT" + }, "node_modules/micromark-util-character": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", @@ -2563,6 +2605,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -2935,6 +2986,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" + }, "node_modules/unist-util-is": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", diff --git a/docs/package.json b/docs/package.json index 2a38078d..0f9ef1de 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,6 @@ { "name": "facturxdotnet-docs", - "version": "0.0.0.0", + "version": "0.0.0", "type": "module", "scripts": { "dev": "vitepress dev --port 40850", @@ -8,11 +8,11 @@ "preview": "vitepress preview --port 40850" }, "devDependencies": { - "license-report": "^6.7.2", "vitepress": "^1.6.3", "vitepress-plugin-nprogress": "^0.0.4" }, "dependencies": { + "markdown-it": "^14.1.0", "semver": "^7.7.1", "vitepress-openapi": "^0.0.3-alpha.76", "vitepress-plugin-auto-sidebar": "^1.3.5", diff --git a/docs/src/components/Licenses.vue b/docs/src/components/Licenses.vue deleted file mode 100644 index 63ec6833..00000000 --- a/docs/src/components/Licenses.vue +++ /dev/null @@ -1,21 +0,0 @@ - - - - - diff --git a/docs/src/dependencies.data.ts b/docs/src/dependencies.data.ts index aadb9614..7b6448cd 100644 --- a/docs/src/dependencies.data.ts +++ b/docs/src/dependencies.data.ts @@ -15,61 +15,86 @@ export default { const cliSbom = loadSbom("src/public/cli.bom.json"); const librarySbom = loadSbom("src/public/library.bom.json"); - const result: Dependencies = { + const docsDependencies = [ + ...loadDependenciesFromSbom(docsSbom), + { + name: "docfx", + version: "2.78.3", + author: ".NET Foundation and Contributors", + description: "The docfx command line tool published as .NET tool", + license: "MIT", + link: "https://github.com/dotnet/docfx", + }, + { + name: "DocFxMarkdownGen ", + version: "0.4.2", + author: "Jan0660 ", + description: "Docusaurus Markdown generator using DocFX.", + license: "MIT", + link: "https://github.com/Jan0660/DocFxMarkdownGen", + }, + ]; + const editorDependencies = loadDependenciesFromSbom(editorSbom); + const apiDependencies = loadDependenciesFromSbom(apiSbom); + const cliDependencies = loadDependenciesFromSbom(cliSbom); + const libraryDependencies = loadDependenciesFromSbom(librarySbom); + + return { docs: { sbomLink: "/docs.bom.json", - licenses: groupDependenciesByLicense([ - ...loadDependenciesFromSbom(docsSbom), - { - name: "docfx", - author: ".NET Foundation and Contributors", - version: "2.78.3", - license: "MIT", - link: "https://github.com/dotnet/docfx", - }, - { - name: "DocFxMarkdownGen ", - author: "Jan0660 ", - version: "0.4.2", - license: "MIT", - link: "https://github.com/Jan0660/DocFxMarkdownGen", - }, - ]), + dependenciesCount: docsDependencies.length, + licenses: groupDependenciesByLicense(docsDependencies), }, editor: { sbomLink: "/editor.bom.json", - licenses: groupDependenciesByLicense( - loadDependenciesFromSbom(editorSbom), - ), + dependenciesCount: editorDependencies.length, + licenses: groupDependenciesByLicense(editorDependencies), }, api: { sbomLink: "/api.bom.json", - licenses: groupDependenciesByLicense(loadDependenciesFromSbom(apiSbom)), + dependenciesCount: apiDependencies.length, + licenses: groupDependenciesByLicense(apiDependencies), }, cli: { sbomLink: "/cli.bom.json", - licenses: groupDependenciesByLicense(loadDependenciesFromSbom(cliSbom)), + dependenciesCount: cliDependencies.length, + licenses: groupDependenciesByLicense(cliDependencies), }, library: { sbomLink: "/library.bom.json", - licenses: groupDependenciesByLicense( - loadDependenciesFromSbom(librarySbom), - ), + dependenciesCount: libraryDependencies.length, + licenses: groupDependenciesByLicense(libraryDependencies), }, }; - - console.log(result); - - return result; }, }; interface Dependencies { - docs: { sbomLink: string; licenses: LicenseGroup[] }; - editor: { sbomLink: string; licenses: LicenseGroup[] }; - api: { sbomLink: string; licenses: LicenseGroup[] }; - cli: { sbomLink: string; licenses: LicenseGroup[] }; - library: { sbomLink: string; licenses: LicenseGroup[] }; + docs: { + sbomLink: string; + dependenciesCount: number; + licenses: LicenseGroup[]; + }; + editor: { + sbomLink: string; + dependenciesCount: number; + licenses: LicenseGroup[]; + }; + api: { + sbomLink: string; + dependenciesCount: number; + licenses: LicenseGroup[]; + }; + cli: { + sbomLink: string; + dependenciesCount: number; + licenses: LicenseGroup[]; + }; + library: { + sbomLink: string; + dependenciesCount: number; + licenses: LicenseGroup[]; + }; } interface LicenseGroup { @@ -79,10 +104,11 @@ interface LicenseGroup { interface Dependency { readonly name: string; - readonly author: string; readonly version: string; - readonly license: string; - readonly link: string; + readonly author?: string; + readonly description?: string; + readonly license?: string; + readonly link?: string; } function groupDependenciesByLicense( @@ -102,7 +128,13 @@ function groupDependenciesByLicense( } return Object.values(result).sort((a, b) => - a.license.localeCompare(b.license), + a.license === "" && b.license === "" + ? 0 + : a.license === "" + ? 1 + : b.license === "" + ? -1 + : a.license.localeCompare(b.license), ); } @@ -113,20 +145,24 @@ function loadSbom(path: string) { function loadDependenciesFromSbom(sbom: Sbom): Dependency[] { const thisComponentName = sbom.metadata.component["bom-ref"]; - const thisComponentDependencies = sbom.dependencies[thisComponentName]; + const thisComponentDependencies = sbom.dependencies.find( + d => d.ref === thisComponentName, + )?.dependsOn; + if (thisComponentDependencies === undefined) { return []; } - const dependencies = sbom.components.filter((c) => + const dependencies = sbom.components.filter(c => thisComponentDependencies.includes(c["bom-ref"]), ); return dependencies.map( (component: SbomComponent): Dependency => ({ name: component.name, - author: component.author, version: component.version, + author: component.author, + description: component.description, license: getLicense(component.licenses), link: getLink(component.externalReferences), }), @@ -138,7 +174,7 @@ function getLicense(licenses: SbomLicense[] | undefined): string | undefined { return undefined; } - const licenseNames = licenses.map((license) => { + const licenseNames = licenses.map(license => { if (isSbomLicenseExpression(license)) { if ( license.expression.startsWith("(") && @@ -163,12 +199,12 @@ function getLink( return undefined; } - const vcsLink = externalReferences.find((r) => r.type === "vcs")?.url; + const vcsLink = externalReferences.find(r => r.type === "vcs")?.url; if (vcsLink !== undefined) { return getRepositoryUrl(vcsLink); } - return externalReferences.find((r) => r.type === "website")?.url; + return externalReferences.find(r => r.type === "website")?.url; } const gitPlusUrlRegExp = new RegExp(/git\+(.*)\.git/g); diff --git a/docs/src/guides/about.md b/docs/src/guides/about.md index e22ac5cf..1fd06818 100644 --- a/docs/src/guides/about.md +++ b/docs/src/guides/about.md @@ -1,10 +1,12 @@ --- title: About +outline: deep --- + + + + From b80ad685315c871bc12a80f2feeb3003b65e9442 Mon Sep 17 00:00:00 2001 From: ismailbennani Date: Sat, 26 Apr 2025 22:21:08 +0200 Subject: [PATCH 21/25] update CI to set version of docs in package json --- .github/workflows/reusable-build-docs-application.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/reusable-build-docs-application.yml b/.github/workflows/reusable-build-docs-application.yml index b454fcd4..2592fd6a 100644 --- a/.github/workflows/reusable-build-docs-application.yml +++ b/.github/workflows/reusable-build-docs-application.yml @@ -183,6 +183,9 @@ jobs: cat docs/src/env.json + - name: Set version in package json + run: cd docs; npm run version ${{ inputs.version }} + - name: Build run: cd docs; npm run build -- --base /docs/ --outDir dist/ From c766eaebf859f864db84eababe4e93e0839ed6ec Mon Sep 17 00:00:00 2001 From: ismailbennani Date: Sat, 26 Apr 2025 22:23:51 +0200 Subject: [PATCH 22/25] fix ci --- .github/workflows/reusable-build-docs-application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-build-docs-application.yml b/.github/workflows/reusable-build-docs-application.yml index 2592fd6a..167bf43b 100644 --- a/.github/workflows/reusable-build-docs-application.yml +++ b/.github/workflows/reusable-build-docs-application.yml @@ -184,7 +184,7 @@ jobs: cat docs/src/env.json - name: Set version in package json - run: cd docs; npm run version ${{ inputs.version }} + run: cd docs; npm version ${{ inputs.version }} - name: Build run: cd docs; npm run build -- --base /docs/ --outDir dist/ From 881998fc5d6ab9c022ab0feb2300ce33669c6dc2 Mon Sep 17 00:00:00 2001 From: ismailbennani Date: Sat, 26 Apr 2025 22:28:57 +0200 Subject: [PATCH 23/25] use href correctly in sbom download link --- docs/src/guides/components/Dependencies.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/src/guides/components/Dependencies.vue b/docs/src/guides/components/Dependencies.vue index 3f28ed20..dd46c630 100644 --- a/docs/src/guides/components/Dependencies.vue +++ b/docs/src/guides/components/Dependencies.vue @@ -1,4 +1,5 @@