Upgrade the third-party library license generation
This commit is contained in:
parent
b57c53a427
commit
14de67c5a7
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import { spawnSync } from "child_process";
|
import { spawnSync } from "child_process";
|
||||||
|
|
||||||
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||||
|
|
@ -87,6 +88,7 @@ export default defineConfig({
|
||||||
type LicenseInfo = {
|
type LicenseInfo = {
|
||||||
licenseName: string;
|
licenseName: string;
|
||||||
licenseText: string;
|
licenseText: string;
|
||||||
|
noticeText?: string;
|
||||||
packages: PackageInfo[];
|
packages: PackageInfo[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -117,70 +119,132 @@ function formatThirdPartyLicenses(jsLicenses: Dependency[]): string {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find then duplicate this license if one of its packages is `path-bool`, adding its notice text.
|
||||||
|
let foundLicensesIndex;
|
||||||
|
let foundPackagesIndex;
|
||||||
|
licenses.forEach((license, licenseIndex) => {
|
||||||
|
license.packages.forEach((pkg, pkgIndex) => {
|
||||||
|
if (pkg.name === "path-bool") {
|
||||||
|
foundLicensesIndex = licenseIndex;
|
||||||
|
foundPackagesIndex = pkgIndex;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (foundLicensesIndex !== undefined && foundPackagesIndex !== undefined) {
|
||||||
|
const license = licenses[foundLicensesIndex];
|
||||||
|
const pkg = license.packages[foundPackagesIndex];
|
||||||
|
|
||||||
|
license.packages = license.packages.filter((pkg) => pkg.name !== "path-bool");
|
||||||
|
const noticeText = fs.readFileSync(path.resolve(__dirname, "../libraries/path-bool/NOTICE"), "utf8");
|
||||||
|
|
||||||
|
licenses.push({
|
||||||
|
licenseName: license.licenseName,
|
||||||
|
licenseText: license.licenseText,
|
||||||
|
noticeText,
|
||||||
|
packages: [pkg],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Augment the imported Rust license list with the provided JS license list.
|
// Augment the imported Rust license list with the provided JS license list.
|
||||||
jsLicenses.forEach((jsLicense) => {
|
jsLicenses.forEach((jsLicense) => {
|
||||||
const name = jsLicense.name || "";
|
const name = jsLicense.name || "";
|
||||||
const version = jsLicense.version || "";
|
const version = jsLicense.version || "";
|
||||||
const author = jsLicense.author?.text() || "";
|
const author = jsLicense.author?.text() || "";
|
||||||
const licenseText = trimBlankLines(jsLicense.licenseText ?? "");
|
|
||||||
const licenseName = jsLicense.license || "";
|
const licenseName = jsLicense.license || "";
|
||||||
|
const licenseText = trimBlankLines(jsLicense.licenseText || "");
|
||||||
|
const noticeText = trimBlankLines(jsLicense.noticeText || "");
|
||||||
|
|
||||||
let repository = jsLicense.repository || "";
|
let repository = jsLicense.repository || "";
|
||||||
if (repository && typeof repository === "object") repository = repository.url;
|
if (repository && typeof repository === "object") repository = repository.url;
|
||||||
|
|
||||||
// Remove the `git+` or `git://` prefix and `.git` suffix.
|
// Remove the `git+` or `git://` prefix and `.git` suffix.
|
||||||
const repo = repository ? repository.replace(/^.*(github.com\/.*?\/.*?)(?:.git)/, "https://$1") : repository;
|
const repo = repository ? repository.replace(/^.*(github.com\/.*?\/.*?)(?:.git)/, "https://$1") : repository;
|
||||||
|
|
||||||
const matchedLicense = licenses.find((license) => trimBlankLines(license.licenseText || "") === licenseText);
|
const matchedLicense = licenses.find(
|
||||||
|
(license) => license.licenseName === licenseName && trimBlankLines(license.licenseText || "") === licenseText && trimBlankLines(license.noticeText || "") === noticeText,
|
||||||
|
);
|
||||||
|
|
||||||
const packages: PackageInfo = { name, version, author, repository: repo };
|
const pkg: PackageInfo = { name, version, author, repository: repo };
|
||||||
if (matchedLicense) matchedLicense.packages.push(packages);
|
if (matchedLicense) matchedLicense.packages.push(pkg);
|
||||||
else licenses.push({ licenseName, licenseText, packages: [packages] });
|
else licenses.push({ licenseName, licenseText, noticeText, packages: [pkg] });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Combine any license notices into the license text.
|
||||||
|
licenses.forEach((license, index) => {
|
||||||
|
if (license.noticeText) {
|
||||||
|
licenses[index].licenseText += "\n\n";
|
||||||
|
licenses[index].licenseText += " _______________________________________\n";
|
||||||
|
licenses[index].licenseText += "│ │\n";
|
||||||
|
licenses[index].licenseText += "│ THE FOLLOWING NOTICE FILE IS INCLUDED │\n";
|
||||||
|
licenses[index].licenseText += "│ │\n";
|
||||||
|
licenses[index].licenseText += " ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\n\n";
|
||||||
|
licenses[index].licenseText += `${license.noticeText}\n`;
|
||||||
|
licenses[index].noticeText = undefined;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// De-duplicate any licenses with the same text by merging their lists of packages.
|
// De-duplicate any licenses with the same text by merging their lists of packages.
|
||||||
licenses.forEach((license, licenseIndex) => {
|
const licensesNormalizedWhitespace = licenses.map((license) => license.licenseText.replace(/[\n\s]+/g, " ").trim());
|
||||||
licenses.slice(0, licenseIndex).forEach((comparisonLicense) => {
|
licenses.forEach((currentLicense, currentLicenseIndex) => {
|
||||||
if (license.licenseText === comparisonLicense.licenseText) {
|
licenses.slice(0, currentLicenseIndex).forEach((comparisonLicense, comparisonLicenseIndex) => {
|
||||||
license.packages.push(...comparisonLicense.packages);
|
if (licensesNormalizedWhitespace[currentLicenseIndex] === licensesNormalizedWhitespace[comparisonLicenseIndex]) {
|
||||||
|
currentLicense.packages.push(...comparisonLicense.packages);
|
||||||
comparisonLicense.packages = [];
|
comparisonLicense.packages = [];
|
||||||
// After emptying the packages, the redundant license with no packages will be removed in the next step's `filter()`.
|
// After emptying the packages, the redundant license with no packages will be removed in the next step's `filter()`.
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Filter out the internal Graphite crates, which are not third-party.
|
// Filter out first-party internal Graphite crates.
|
||||||
licenses = licenses.filter((license) => {
|
licenses = licenses.filter((license) => {
|
||||||
license.packages = license.packages.filter(
|
license.packages = license.packages.filter(
|
||||||
(packageInfo) =>
|
(packageInfo) =>
|
||||||
!(packageInfo.repository && packageInfo.repository.toLowerCase().includes("github.com/GraphiteEditor/Graphite".toLowerCase())) &&
|
!(packageInfo.repository && packageInfo.repository.toLowerCase().includes("github.com/GraphiteEditor/Graphite".toLowerCase())) &&
|
||||||
!(packageInfo.author && packageInfo.author.toLowerCase().includes("contact@graphite.rs")),
|
!(
|
||||||
|
packageInfo.author &&
|
||||||
|
packageInfo.author.toLowerCase().includes("contact@graphite.rs") &&
|
||||||
|
// Exclude a comma which indicates multiple authors, which we need to not filter out
|
||||||
|
!packageInfo.author.toLowerCase().includes(",")
|
||||||
|
),
|
||||||
);
|
);
|
||||||
return license.packages.length > 0;
|
return license.packages.length > 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sort the licenses, and the packages using each license, alphabetically.
|
// Sort the licenses by the number of packages using the same license, and then alphabetically by license name.
|
||||||
licenses.sort((a, b) => a.licenseName.localeCompare(b.licenseName));
|
|
||||||
licenses.sort((a, b) => a.licenseText.localeCompare(b.licenseText));
|
licenses.sort((a, b) => a.licenseText.localeCompare(b.licenseText));
|
||||||
|
licenses.sort((a, b) => a.licenseName.localeCompare(b.licenseName));
|
||||||
|
licenses.sort((a, b) => b.packages.length - a.packages.length);
|
||||||
|
// Sort the individual packages using each license alphabetically.
|
||||||
licenses.forEach((license) => {
|
licenses.forEach((license) => {
|
||||||
license.packages.sort((a, b) => a.name.localeCompare(b.name));
|
license.packages.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Append a block for each license shared by multiple packages with identical license text.
|
// Prepare a header for the license notice.
|
||||||
let formattedLicenseNotice = "GRAPHITE THIRD-PARTY SOFTWARE LICENSE NOTICES";
|
let formattedLicenseNotice = "";
|
||||||
licenses.forEach((license) => {
|
formattedLicenseNotice += "▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐\n";
|
||||||
let packagesWithSameLicense = "";
|
formattedLicenseNotice += "▐▐ ▐▐\n";
|
||||||
license.packages.forEach((packageInfo) => {
|
formattedLicenseNotice += "▐▐ GRAPHITE THIRD-PARTY SOFTWARE LICENSE NOTICES ▐▐\n";
|
||||||
const { name, version, author, repository } = packageInfo;
|
formattedLicenseNotice += "▐▐ ▐▐\n";
|
||||||
packagesWithSameLicense += `${name} ${version}${author ? ` - ${author}` : ""}${repository ? ` - ${repository}` : ""}\n`;
|
formattedLicenseNotice += "▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐▐\n";
|
||||||
});
|
|
||||||
packagesWithSameLicense = packagesWithSameLicense.trim();
|
|
||||||
const packagesLineLength = Math.max(...packagesWithSameLicense.split("\n").map((line) => line.length));
|
|
||||||
|
|
||||||
formattedLicenseNotice += "\n\n--------------------------------------------------------------------------------\n\n";
|
// Append a block for each license shared by multiple packages with identical license text.
|
||||||
formattedLicenseNotice += `The following packages are licensed under the terms of the ${license.licenseName} license as printed beneath:\n`;
|
licenses.forEach((license) => {
|
||||||
formattedLicenseNotice += `${"_".repeat(packagesLineLength)}\n`;
|
let packagesWithSameLicense = license.packages.map((packageInfo) => {
|
||||||
formattedLicenseNotice += `${packagesWithSameLicense}\n`;
|
const { name, version, author, repository } = packageInfo;
|
||||||
formattedLicenseNotice += `${"‾".repeat(packagesLineLength)}\n`;
|
return `${name} ${version}${author ? ` - ${author}` : ""}${repository ? ` - ${repository}` : ""}`;
|
||||||
|
});
|
||||||
|
const multi = packagesWithSameLicense.length !== 1;
|
||||||
|
const saysLicense = license.licenseName.toLowerCase().includes("license");
|
||||||
|
const header = `The package${multi ? "s" : ""} listed here ${multi ? "are" : "is"} licensed under the terms of the ${license.licenseName}${saysLicense ? "" : " license"} printed beneath`;
|
||||||
|
const packagesLineLength = Math.max(header.length, ...packagesWithSameLicense.map((line) => line.length));
|
||||||
|
packagesWithSameLicense = packagesWithSameLicense.map((line) => `│ ${line}${" ".repeat(packagesLineLength - line.length)} │`);
|
||||||
|
|
||||||
|
formattedLicenseNotice += "\n";
|
||||||
|
formattedLicenseNotice += ` ${"_".repeat(packagesLineLength + 2)}\n`;
|
||||||
|
formattedLicenseNotice += `│ ${" ".repeat(packagesLineLength)} │\n`;
|
||||||
|
formattedLicenseNotice += `│ ${header}${" ".repeat(packagesLineLength - header.length)} │\n`;
|
||||||
|
formattedLicenseNotice += `│${"_".repeat(packagesLineLength + 2)}│\n`;
|
||||||
|
formattedLicenseNotice += `${packagesWithSameLicense.join("\n")}\n`;
|
||||||
|
formattedLicenseNotice += ` ${"‾".repeat(packagesLineLength + 2)}\n`;
|
||||||
formattedLicenseNotice += `${license.licenseText}\n`;
|
formattedLicenseNotice += `${license.licenseText}\n`;
|
||||||
});
|
});
|
||||||
return formattedLicenseNotice;
|
return formattedLicenseNotice;
|
||||||
|
|
@ -262,10 +326,8 @@ function htmlDecode(input: string): string {
|
||||||
};
|
};
|
||||||
|
|
||||||
return input.replace(/&([^;]+);/g, (entity: string, entityCode: string) => {
|
return input.replace(/&([^;]+);/g, (entity: string, entityCode: string) => {
|
||||||
const maybeEntity = Object.keys(htmlEntities).find((key) => key === entityCode);
|
const maybeEntity = Object.entries(htmlEntities).find(([key, _]) => key === entityCode);
|
||||||
if (maybeEntity) {
|
if (maybeEntity) return maybeEntity[1];
|
||||||
return maybeEntity[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
let match;
|
let match;
|
||||||
// eslint-disable-next-line no-cond-assign
|
// eslint-disable-next-line no-cond-assign
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ Raster image editing is a growing capability that will develop over time into th
|
||||||
|
|
||||||
A prototype Brush tool exists letting you draw simple doodles and sketches. However it is very limited in its capabilities and there are multiple bugs and performance issues with the feature. It can be used in a limited capacity, but don't expect to paint anything too impressive using raster brushes quite yet.
|
A prototype Brush tool exists letting you draw simple doodles and sketches. However it is very limited in its capabilities and there are multiple bugs and performance issues with the feature. It can be used in a limited capacity, but don't expect to paint anything too impressive using raster brushes quite yet.
|
||||||
|
|
||||||
The raster-based Imaginate feature enables you to synthesize artwork using generative AI based on text descriptions. With it, you can also nondestructively modify your vector art and imported images. You can inpaint (or outpaint) the content in a specific masked part of an image or use it to touch up quick-and-dirty compositions. This feature is temporarily out of comission but will be resurrected, and further improved upon, in early 2024.
|
The raster-based Imaginate feature enables you to synthesize artwork using generative AI based on text descriptions. With it, you can also nondestructively modify your vector art and imported images. You can inpaint (or outpaint) the content in a specific masked part of an image or use it to touch up quick-and-dirty compositions. This feature is temporarily out of comission but will be resurrected, and further improved upon, in the near future.
|
||||||
|
|
||||||
### Procedural design
|
### Procedural design
|
||||||
|
|
||||||
|
|
@ -69,11 +69,11 @@ Saved documents will eventually fail to render in future versions of the Graphit
|
||||||
|
|
||||||
### Limited raster capabilities
|
### Limited raster capabilities
|
||||||
|
|
||||||
While you can import bitmap images, apply image effects in the node graph, and draw brush strokes, there is not much tooling yet to make the overall raster workflow that useful. Marquee selection is an upcoming feature in the first half of 2024 which will significantly improve the utility of raster editing in Graphite. Hardware accelerated rendering, to offload work from the CPU to GPU, is also planned for early 2024 which will drastically improve the performance.
|
While you can import bitmap images, apply image effects in the node graph, and draw brush strokes, there is not much tooling yet to make the overall raster workflow that useful. Marquee selection is an upcoming feature in the first half of 2025 which will significantly improve the utility of raster editing in Graphite. Hardware accelerated rendering, to offload work from the CPU to GPU, is also planned for early 2025 which will drastically improve the performance of working with millions of pixels.
|
||||||
|
|
||||||
### Performance bottlenecks
|
### Performance bottlenecks
|
||||||
|
|
||||||
Graphite has several temporary performance bottlenecks that currently yield poor performance when working with raster content, complex vector artwork, and large procedural node graphs. Each of these limitations will be resolved by finishing the implementations of the incomplete systems that impose slowdowns in their current forms. For example, caching in the node graph isn't operational and GPU-accelerated rendering isn't enabled yet. This will be a central focus throughout 2024.
|
Graphite has several temporary performance bottlenecks that currently yield poor performance when working with raster content, complex vector artwork, and large procedural node graphs. Each of these limitations will be resolved by finishing the implementations of the incomplete systems that impose slowdowns in their current forms. For example, caching in the node graph isn't operational and GPU-accelerated rendering isn't enabled yet. This will be a central focus throughout 2024 and 2025.
|
||||||
|
|
||||||
### Best-effort Safari support
|
### Best-effort Safari support
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue