diff --git a/.github/workflows/reusable-build-docs-application.yml b/.github/workflows/reusable-build-docs-application.yml index af841d0f..6e23560b 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: @@ -70,8 +65,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 - name: Generate .NET API reference run: | @@ -155,53 +150,23 @@ jobs: - name: Install dependencies run: cd docs; npm ci - - name: Install dependencies in editor (for license-report) + - name: Install dependencies in editor (for sbom generation) run: cd src/FacturXDotNet.WebEditor; npm ci - - name: Install license-report - run: npm i -g license-report + - name: Generate Editor SBOM + run: cd src/FacturXDotNet.WebEditor; npm sbom --sbom-format cyclonedx > ../../docs/src/public/editor.bom.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 Docs SBOM + run: cd docs; npm sbom --sbom-format cyclonedx > src/public/docs.bom.json + + - name: Generate API SBOM + run: cd src/FacturXDotNet.API; dotnet-CycloneDX FacturXDotNet.API.csproj -o ../../docs/src/public --json -fn api.bom.json + + - name: Generate CLI SBOM + run: cd src/FacturXDotNet.CLI; dotnet-CycloneDX FacturXDotNet.CLI.csproj -o ../../docs/src/public --json -fn cli.bom.json + + - name: Generate Library SBOM + run: cd src/FacturXDotNet; dotnet-CycloneDX FacturXDotNet.csproj -o ../../docs/src/public --json -fn library.bom.json - name: Write env.json run: | @@ -209,7 +174,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 @@ -218,6 +183,9 @@ jobs: cat docs/src/env.json + - name: Set version in package json + run: cd docs; npm version ${{ inputs.version }} + - name: Build run: cd docs; npm run build -- --base /docs/ --outDir dist/ diff --git a/.github/workflows/reusable-build-publish-api-docker-image.yml b/.github/workflows/reusable-build-publish-api-docker-image.yml index b510045d..c01334eb 100644 --- a/.github/workflows/reusable-build-publish-api-docker-image.yml +++ b/.github/workflows/reusable-build-publish-api-docker-image.yml @@ -48,16 +48,16 @@ jobs: with: fetch-depth: 0 - - name: Setup .NET 6 (for dotnet-project-licenses) + - name: Setup .NET 9 (for CycloneDX) uses: actions/setup-dotnet@v4 with: - dotnet-version: 6.0.x + dotnet-version: 9.0.x - - name: Install dotnet-project-licenses - run: dotnet tool install -g dotnet-project-licenses --framework net6.0 + - name: Install CycloneDX + 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 }} @@ -88,7 +88,7 @@ jobs: package-name: facturxdotnet-api tag: ${{ inputs.tag }} github-token: ${{ secrets.GITHUB_TOKEN }} - + - name: Push ${{ inputs.tag }} if: inputs.tag != '' run: | diff --git a/.github/workflows/reusable-compute-version.yml b/.github/workflows/reusable-compute-version.yml index f434a375..1b62b785 100644 --- a/.github/workflows/reusable-compute-version.yml +++ b/.github/workflows/reusable-compute-version.yml @@ -19,7 +19,7 @@ env: jobs: compute_version: - name: Build Docker Image + name: Compute version runs-on: ubuntu-latest outputs: version: ${{ steps.compute_version.outputs.version }} diff --git a/docs/.gitignore b/docs/.gitignore index 68157175..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-licenses.json -src/assets/editor-licenses.json -src/assets/api-licenses.json -src/assets/cli-licenses.json -src/assets/library-licenses.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/.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/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-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 d26ecc87..0f9ef1de 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,4 +1,6 @@ { + "name": "facturxdotnet-docs", + "version": "0.0.0", "type": "module", "scripts": { "dev": "vitepress dev --port 40850", @@ -6,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 6c59f222..7b6448cd 100644 --- a/docs/src/dependencies.data.ts +++ b/docs/src/dependencies.data.ts @@ -1,57 +1,100 @@ 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 { + 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 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: groupDependenciesByLicense([ - ...loadDependenciesFromLicenseReportOutput( - "src/assets/docs-licenses.json", - ), - { - 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: groupDependenciesByLicense( - loadDependenciesFromDotNetProjectLicensesOutput( - "src/assets/api-licenses.json", - ), - ), - cli: groupDependenciesByLicense( - loadDependenciesFromDotNetProjectLicensesOutput( - "src/assets/cli-licenses.json", - ), - ), - library: groupDependenciesByLicense( - loadDependenciesFromDotNetProjectLicensesOutput( - "src/assets/library-licenses.json", - ), - ), + docs: { + sbomLink: "/docs.bom.json", + dependenciesCount: docsDependencies.length, + licenses: groupDependenciesByLicense(docsDependencies), + }, + editor: { + sbomLink: "/editor.bom.json", + dependenciesCount: editorDependencies.length, + licenses: groupDependenciesByLicense(editorDependencies), + }, + api: { + sbomLink: "/api.bom.json", + dependenciesCount: apiDependencies.length, + licenses: groupDependenciesByLicense(apiDependencies), + }, + cli: { + sbomLink: "/cli.bom.json", + dependenciesCount: cliDependencies.length, + licenses: groupDependenciesByLicense(cliDependencies), + }, + library: { + sbomLink: "/library.bom.json", + dependenciesCount: libraryDependencies.length, + licenses: groupDependenciesByLicense(libraryDependencies), + }, }; }, }; interface Dependencies { - docs: LicenseGroup[]; - editor: LicenseGroup[]; - api: LicenseGroup[]; - cli: LicenseGroup[]; - library: 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 { @@ -61,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( @@ -84,70 +128,87 @@ 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), ); } -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.find( + d => d.ref === thisComponentName, + )?.dependsOn; -interface LicenseReportOutputElement { - readonly name: string; - readonly author: string; - readonly installedVersion: string; - readonly licenseType: string; - readonly link: string; -} + if (thisComponentDependencies === undefined) { + return []; + } -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), + const dependencies = sbom.components.filter(c => + thisComponentDependencies.includes(c["bom-ref"]), + ); + + return dependencies.map( + (component: SbomComponent): Dependency => ({ + name: component.name, + version: component.version, + author: component.author, + description: component.description, + 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 +227,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/docs/src/guides/about.md b/docs/src/guides/about.md index 27edfbb9..1fd06818 100644 --- a/docs/src/guides/about.md +++ b/docs/src/guides/about.md @@ -1,10 +1,12 @@ --- title: About +outline: deep --- + + + + diff --git a/src/.idea/.idea.FacturXDotNet/.idea/encodings.xml b/src/.idea/.idea.FacturXDotNet/.idea/encodings.xml new file mode 100644 index 00000000..7261df15 --- /dev/null +++ b/src/.idea/.idea.FacturXDotNet/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file 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 cc7b1413..08ff15ab 100644 --- a/src/FacturXDotNet.WebEditor/.gitignore +++ b/src/FacturXDotNet.WebEditor/.gitignore @@ -40,3 +40,5 @@ testem.log # System files .DS_Store Thumbs.db + +src/dependencies/sbom.json diff --git a/src/FacturXDotNet.WebEditor/package-lock.json b/src/FacturXDotNet.WebEditor/package-lock.json index 7d28246d..b0e64034 100644 --- a/src/FacturXDotNet.WebEditor/package-lock.json +++ b/src/FacturXDotNet.WebEditor/package-lock.json @@ -19,14 +19,17 @@ "@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", "marked": "^15.0.8", + "minisearch": "^7.1.2", "ngx-filesize": "^3.0.5", "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" }, @@ -42,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" @@ -3503,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", @@ -5202,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", @@ -5289,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", @@ -5322,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", @@ -5846,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", @@ -5932,6 +5887,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", @@ -6977,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", @@ -8367,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", @@ -8449,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", @@ -8839,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", @@ -9457,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", @@ -9631,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", @@ -9753,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", @@ -10066,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", @@ -10496,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", @@ -10765,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", @@ -11192,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", @@ -11365,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", @@ -11706,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", @@ -12002,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", @@ -12206,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", @@ -12508,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", @@ -12747,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", @@ -13100,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", @@ -13836,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", @@ -13885,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", @@ -14084,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", @@ -14143,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", @@ -14590,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", @@ -15214,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", @@ -15397,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", @@ -15450,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", @@ -15629,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", @@ -15984,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", @@ -16052,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 b7d73734..32db7d8d 100644 --- a/src/FacturXDotNet.WebEditor/package.json +++ b/src/FacturXDotNet.WebEditor/package.json @@ -21,14 +21,17 @@ "@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", "marked": "^15.0.8", + "minisearch": "^7.1.2", "ngx-filesize": "^3.0.5", "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 +47,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..34c11a73 100644 --- a/src/FacturXDotNet.WebEditor/scripts/dump-licenses.js +++ b/src/FacturXDotNet.WebEditor/scripts/dump-licenses.js @@ -2,16 +2,11 @@ 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"); - -execSync("npm i -g license-report", { stdio: "inherit" }); -console.log(); - -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 dependenciesFileDirectory = path.join(__dirname, "..", "src", "dependencies"); +fs.mkdirSync(dependenciesFileDirectory, { recursive: true }); + +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}`); 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/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 cc739e9a..5758c19a 100644 --- a/src/FacturXDotNet.WebEditor/src/app/core/api/info.api.ts +++ b/src/FacturXDotNet.WebEditor/src/app/core/api/info.api.ts @@ -1,8 +1,8 @@ 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'; +import { IBuildInformationDto, IHostingInformationDto } from './api.models'; @Injectable({ providedIn: 'root', @@ -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..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,8 @@ import { inject, Injectable } from '@angular/core'; import { InfoApi } from '../info.api'; import { rxResource } from '@angular/core/rxjs-interop'; -import { forkJoin } from 'rxjs'; +import { map, switchMap } from 'rxjs'; +import { Sbom } from '../../sbom'; @Injectable({ providedIn: 'root', @@ -9,7 +10,13 @@ import { forkJoin } from 'rxjs'; export class ApiConstantsService { private infoApi = inject(InfoApi); - info = rxResource({ - loader: () => forkJoin({ build: this.infoApi.getBuildInformation(), hosting: this.infoApi.getHostingInformation(), dependencies: this.infoApi.getDependencies() }), + buildInfo = rxResource({ loader: () => this.infoApi.getBuildInformation() }); + hostingInfo = rxResource({ loader: () => this.infoApi.getHostingInformation() }); + sbom = rxResource({ + loader: () => + this.infoApi.getSbom().pipe( + switchMap((sbomFile) => sbomFile.text()), + map((sbomContent) => JSON.parse(sbomContent) as Sbom), + ), }); } 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/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/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/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/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-licenses.component.ts b/src/FacturXDotNet.WebEditor/src/app/features/about/about-licenses.component.ts deleted file mode 100644 index e2da08f8..00000000 --- a/src/FacturXDotNet.WebEditor/src/app/features/about/about-licenses.component.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Component, computed, input, Signal } from '@angular/core'; -import { IPackageDto } from '../../core/api/api.models'; - -@Component({ - selector: 'app-about-licenses', - 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 }} - -
- } -
-
- } -
- `, -}) -export class AboutLicensesComponent { - packages = input.required(); - - protected packagesRecord: Signal> = computed(() => { - const result: Record = {}; - - for (const p of this.packages()) { - if (!result[p.license]) { - result[p.license] = []; - } - - result[p.license].push(p); - } - - return result; - }); - - protected licenses: Signal = computed(() => { - const result = Object.keys(this.packagesRecord()); - result.sort((a, b) => a.localeCompare(b)); - return result; - }); -} 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 new file mode 100644 index 00000000..9ffe6348 --- /dev/null +++ b/src/FacturXDotNet.WebEditor/src/app/features/about/about-sbom.component.ts @@ -0,0 +1,275 @@ +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'; +import { Sbom } from '../../core/sbom'; +import { Dependency, extractDependenciesFromSbom } from './dependency'; +import { 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, MarkdownComponent, EscapeHtmlPipe], + template: ` +
Dependencies
+
+ +
+ + + +
+
+ +
+
+ +
+
+ +
+
+ +
{{ searchResult().packagesCount }} result(s)
+ + + + +
+ @for (license of record.licenses; track license.license) { +
+ + @if (collapsedBlocks()[license.license]()) { + + } @else { + + } + + @if (license.license === '') { + Unknown + } @else { + {{ license.license }} + } + ({{ license.packages.length }}) + +
    + @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 { + , + } + } + } + } + + @if (package_.latest.author) { + - + + } + + @if (package_.latest.description) { +

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

    + } +
  • + } +
+
+ } +
+
+ `, +}) +export class AboutSbomComponent { + sbom = input.required(); + sbomName = input(); + + 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 allDependenciesCount = computed(() => { + const names = new Set(this.dependencies().map((d) => d.name)); + return names.size; + }); + + protected activeTab = signal<'direct' | 'all'>('direct'); + + private miniSearch = computed(() => { + const packages = this.dependencies().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.dependencies()); + + let packages: (Dependency & { 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 collapsedBlocks: Signal>> = linkedSignal({ + source: this.dependencies, + computation: (source, previous) => { + const licenses = new Set(source.map((p) => licenseNameOrDefault(p.license))); + + const newValue = previous !== undefined ? { ...previous.value } : {}; + for (const license of licenses) { + newValue[license] = newValue[license] ?? signal(false); + } + return newValue; + }, + }); + + protected collapseAll() { + const collapsedBlocks = this.collapsedBlocks(); + for (const value of Object.values(collapsedBlocks)) { + value.set(true); + } + } + + protected expandAll() { + const collapsedBlocks = this.collapsedBlocks(); + for (const value of Object.values(collapsedBlocks)) { + value.set(false); + } + } + + 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); + return; + } + + this.searchTerm.set(term); + } +} + +interface DependenciesGroupedByLicensesThenName { + packagesCount: number; + licenses: { + license: string; + packages: { + name: string; + versions: string[]; + latest: TPackage; + packages: TPackage[]; + }[]; + }[]; +} + +function groupPackages(packages: TPackage[], keepLatestOnly: boolean = false): DependenciesGroupedByLicensesThenName { + const licenses: Record> = {}; + + for (const p of packages) { + const license = licenseNameOrDefault(p.license); + + if (!licenses[license]) { + licenses[license] = {}; + } + + if (!licenses[license][p.name]) { + licenses[license][p.name] = []; + } + + 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); + } + } + + return { + 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) + .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/about.page.ts b/src/FacturXDotNet.WebEditor/src/app/features/about/about.page.ts index 794451db..e0488fbf 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,13 @@ import { Component, computed, inject } from '@angular/core'; import { RouterLink } from '@angular/router'; -import { AboutLicensesComponent } 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 licenses from '../../../licenses/licenses.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 { IPackageDto } from '../../core/api/api.models'; +import { getApiErrorMessage } from '../../core/api/api-errors'; @Component({ selector: 'app-about', @@ -94,7 +94,7 @@ import { IPackageDto } from '../../core/api/api.models';
-
+

API

@@ -106,7 +106,7 @@ import { IPackageDto } from '../../core/api/api.models';

- @if (apiConstants.isLoading()) { + @if (buildInfo.isLoading()) {

@@ -133,26 +133,40 @@ import { IPackageDto } from '../../core/api/api.models';

} @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 }} }

+ } -

Dependencies

- + @if (sbom.isLoading()) { +
+ Loading... +
+ } @else if (sbom.error()) { +
+ Failed to load dependencies: +
+ {{ getApiErrorMessage(sbom.error()) }} +
+
+ } @else { + @if (sbom.value(); as sbom) { + + } } }
-
+

Web Editor

@@ -173,24 +187,17 @@ import { IPackageDto } from '../../core/api/api.models'; on GitHub.

-

Dependencies

- +
`, - imports: [RouterLink, AboutLicensesComponent, DatePipe, ApiServerStatusComponent, NgOptimizedImage], + imports: [RouterLink, AboutSbomComponent, 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 webEditorSbom = sbom; protected webEditorVersion = removeBuildInformation(environment.version); protected webEditorBuildMetadata = extractBuildInformation(environment.version); @@ -199,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/about/dependency.ts b/src/FacturXDotNet.WebEditor/src/app/features/about/dependency.ts new file mode 100644 index 00000000..ab5ac8d3 --- /dev/null +++ b/src/FacturXDotNet.WebEditor/src/app/features/about/dependency.ts @@ -0,0 +1,89 @@ +import { isSbomLicenseExpression, Sbom, SbomComponent, SbomExternalReference, SbomLicense } 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), + link: getLink(component.externalReferences), + direct: isDirectDependency(sbom, component), + }; + }); +} + +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 '); +} + +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; +} + +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']); +} + +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; +} 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..3ef93e7e 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'; @@ -68,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); 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'; 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.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; +} 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