mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-14 10:34:06 +02:00
Prettier 2: Electric Boogaloo (#6113)
# Description of Changes When I added Prettier formatting in #6052, my aim was to use just the default settings in Prettier. Turns out, Prettier looks _really hard_ for any config files if it's not explicitly given one, which means that if a developer has some sort of Prettier config file lying around on their system, Prettier might find it and use it. Also, Prettier changes its defaults based on stuff in `.editorconfig` without any good way of disabling that behaviour explicitly in its config file. To solve both of these issues, I've introduced a `.prettierrc` file which sets Prettier's defaults explicitly, and then reformatted all our code _again_ in Prettier's actual default settings. This should achieve the aim of #6052 and remove the possibility for it breaking on different dev computers.
This commit is contained in:
@@ -10,9 +10,19 @@ const frontendDir = process.cwd();
|
||||
const tauriDir = resolve(frontendDir, "src-tauri");
|
||||
const provisionerManifest = join(tauriDir, "provisioner", "Cargo.toml");
|
||||
|
||||
execFileSync("cargo", ["build", "--release", "--manifest-path", provisionerManifest], { stdio: "inherit" });
|
||||
execFileSync(
|
||||
"cargo",
|
||||
["build", "--release", "--manifest-path", provisionerManifest],
|
||||
{ stdio: "inherit" },
|
||||
);
|
||||
|
||||
const provisionerExe = join(tauriDir, "provisioner", "target", "release", "stirling-provisioner.exe");
|
||||
const provisionerExe = join(
|
||||
tauriDir,
|
||||
"provisioner",
|
||||
"target",
|
||||
"release",
|
||||
"stirling-provisioner.exe",
|
||||
);
|
||||
if (!existsSync(provisionerExe)) {
|
||||
throw new Error(`Provisioner binary not found at ${provisionerExe}`);
|
||||
}
|
||||
@@ -26,9 +36,19 @@ copyFileSync(provisionerExe, destExe);
|
||||
// --- Thumbnail handler DLL ---
|
||||
const thumbManifest = join(tauriDir, "thumbnail-handler", "Cargo.toml");
|
||||
|
||||
execFileSync("cargo", ["build", "--release", "--manifest-path", thumbManifest], { stdio: "inherit" });
|
||||
execFileSync(
|
||||
"cargo",
|
||||
["build", "--release", "--manifest-path", thumbManifest],
|
||||
{ stdio: "inherit" },
|
||||
);
|
||||
|
||||
const thumbDll = join(tauriDir, "thumbnail-handler", "target", "release", "stirling_thumbnail_handler.dll");
|
||||
const thumbDll = join(
|
||||
tauriDir,
|
||||
"thumbnail-handler",
|
||||
"target",
|
||||
"release",
|
||||
"stirling_thumbnail_handler.dll",
|
||||
);
|
||||
if (!existsSync(thumbDll)) {
|
||||
throw new Error(`Thumbnail handler DLL not found at ${thumbDll}`);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
// Check for verbose flag
|
||||
const isVerbose = process.argv.includes("--verbose") || process.argv.includes("-v");
|
||||
const isVerbose =
|
||||
process.argv.includes("--verbose") || process.argv.includes("-v");
|
||||
|
||||
// Logging functions
|
||||
const info = (message) => console.log(message);
|
||||
@@ -41,62 +42,82 @@ function scanForUsedIcons() {
|
||||
const content = fs.readFileSync(filePath, "utf8");
|
||||
|
||||
// Match LocalIcon usage: <LocalIcon icon="icon-name" ...>
|
||||
const localIconMatches = content.match(/<LocalIcon\s+[^>]*icon="([^"]+)"/g);
|
||||
const localIconMatches = content.match(
|
||||
/<LocalIcon\s+[^>]*icon="([^"]+)"/g,
|
||||
);
|
||||
if (localIconMatches) {
|
||||
localIconMatches.forEach((match) => {
|
||||
const iconMatch = match.match(/icon="([^"]+)"/);
|
||||
if (iconMatch) {
|
||||
usedIcons.add(iconMatch[1]);
|
||||
debug(` Found: ${iconMatch[1]} in ${path.relative(srcDir, filePath)}`);
|
||||
debug(
|
||||
` Found: ${iconMatch[1]} in ${path.relative(srcDir, filePath)}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Match LocalIcon usage: <LocalIcon icon='icon-name' ...>
|
||||
const localIconSingleQuoteMatches = content.match(/<LocalIcon\s+[^>]*icon='([^']+)'/g);
|
||||
const localIconSingleQuoteMatches = content.match(
|
||||
/<LocalIcon\s+[^>]*icon='([^']+)'/g,
|
||||
);
|
||||
if (localIconSingleQuoteMatches) {
|
||||
localIconSingleQuoteMatches.forEach((match) => {
|
||||
const iconMatch = match.match(/icon='([^']+)'/);
|
||||
if (iconMatch) {
|
||||
usedIcons.add(iconMatch[1]);
|
||||
debug(` Found: ${iconMatch[1]} in ${path.relative(srcDir, filePath)}`);
|
||||
debug(
|
||||
` Found: ${iconMatch[1]} in ${path.relative(srcDir, filePath)}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Match old material-symbols-rounded spans: <span className="material-symbols-rounded">icon-name</span>
|
||||
const spanMatches = content.match(/<span[^>]*className="[^"]*material-symbols-rounded[^"]*"[^>]*>([^<]+)<\/span>/g);
|
||||
const spanMatches = content.match(
|
||||
/<span[^>]*className="[^"]*material-symbols-rounded[^"]*"[^>]*>([^<]+)<\/span>/g,
|
||||
);
|
||||
if (spanMatches) {
|
||||
spanMatches.forEach((match) => {
|
||||
const iconMatch = match.match(/>([^<]+)<\/span>/);
|
||||
if (iconMatch && iconMatch[1].trim()) {
|
||||
const iconName = iconMatch[1].trim();
|
||||
usedIcons.add(iconName);
|
||||
debug(` Found (legacy): ${iconName} in ${path.relative(srcDir, filePath)}`);
|
||||
debug(
|
||||
` Found (legacy): ${iconName} in ${path.relative(srcDir, filePath)}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Match Icon component usage: <Icon icon="material-symbols:icon-name" ...>
|
||||
const iconMatches = content.match(/<Icon\s+[^>]*icon="material-symbols:([^"]+)"/g);
|
||||
const iconMatches = content.match(
|
||||
/<Icon\s+[^>]*icon="material-symbols:([^"]+)"/g,
|
||||
);
|
||||
if (iconMatches) {
|
||||
iconMatches.forEach((match) => {
|
||||
const iconMatch = match.match(/icon="material-symbols:([^"]+)"/);
|
||||
if (iconMatch) {
|
||||
usedIcons.add(iconMatch[1]);
|
||||
debug(` Found (Icon): ${iconMatch[1]} in ${path.relative(srcDir, filePath)}`);
|
||||
debug(
|
||||
` Found (Icon): ${iconMatch[1]} in ${path.relative(srcDir, filePath)}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Match icon config usage: icon: 'icon-name' or icon: "icon-name"
|
||||
const iconPropertyMatches = content.match(/icon:\s*(['"])([a-z0-9-]+)\1/g);
|
||||
const iconPropertyMatches = content.match(
|
||||
/icon:\s*(['"])([a-z0-9-]+)\1/g,
|
||||
);
|
||||
if (iconPropertyMatches) {
|
||||
iconPropertyMatches.forEach((match) => {
|
||||
const iconMatch = match.match(/icon:\s*(['"])([a-z0-9-]+)\1/);
|
||||
if (iconMatch) {
|
||||
usedIcons.add(iconMatch[2]);
|
||||
debug(` Found (config): ${iconMatch[2]} in ${path.relative(srcDir, filePath)}`);
|
||||
debug(
|
||||
` Found (config): ${iconMatch[2]} in ${path.relative(srcDir, filePath)}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -118,7 +139,13 @@ async function main() {
|
||||
const usedIcons = scanForUsedIcons();
|
||||
|
||||
// Check if we need to regenerate (compare with existing)
|
||||
const outputPath = path.join(__dirname, "..", "src", "assets", "material-symbols-icons.json");
|
||||
const outputPath = path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"src",
|
||||
"assets",
|
||||
"material-symbols-icons.json",
|
||||
);
|
||||
let needsRegeneration = true;
|
||||
|
||||
if (fs.existsSync(outputPath)) {
|
||||
@@ -159,11 +186,17 @@ async function main() {
|
||||
|
||||
// Check for missing icons
|
||||
const extractedIconNames = Object.keys(extractedIcons.icons || {});
|
||||
const missingIcons = usedIcons.filter((icon) => !extractedIconNames.includes(icon));
|
||||
const missingIcons = usedIcons.filter(
|
||||
(icon) => !extractedIconNames.includes(icon),
|
||||
);
|
||||
|
||||
if (missingIcons.length > 0) {
|
||||
info(`⚠️ Missing icons (${missingIcons.length}): ${missingIcons.join(", ")}`);
|
||||
info("💡 These icons don't exist in Material Symbols. Please use available alternatives.");
|
||||
info(
|
||||
`⚠️ Missing icons (${missingIcons.length}): ${missingIcons.join(", ")}`,
|
||||
);
|
||||
info(
|
||||
"💡 These icons don't exist in Material Symbols. Please use available alternatives.",
|
||||
);
|
||||
}
|
||||
|
||||
// Create output directory
|
||||
@@ -175,8 +208,12 @@ async function main() {
|
||||
// Write the extracted icon set to a file (outputPath already defined above)
|
||||
fs.writeFileSync(outputPath, JSON.stringify(extractedIcons, null, 2));
|
||||
|
||||
info(`✅ Successfully extracted ${Object.keys(extractedIcons.icons || {}).length} icons`);
|
||||
info(`📦 Bundle size: ${Math.round(JSON.stringify(extractedIcons).length / 1024)}KB`);
|
||||
info(
|
||||
`✅ Successfully extracted ${Object.keys(extractedIcons.icons || {}).length} icons`,
|
||||
);
|
||||
info(
|
||||
`📦 Bundle size: ${Math.round(JSON.stringify(extractedIcons).length / 1024)}KB`,
|
||||
);
|
||||
info(`💾 Saved to: ${outputPath}`);
|
||||
|
||||
// Generate TypeScript types
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { execSync } = require("node:child_process");
|
||||
const { existsSync, mkdirSync, writeFileSync, readFileSync } = require("node:fs");
|
||||
const {
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
writeFileSync,
|
||||
readFileSync,
|
||||
} = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const { argv } = require("node:process");
|
||||
@@ -16,7 +21,13 @@ const POSTPROCESS_ONLY = !!INPUT_FILE;
|
||||
* This script creates a JSON file similar to the Java backend's 3rdPartyLicenses.json
|
||||
*/
|
||||
|
||||
const OUTPUT_FILE = path.join(__dirname, "..", "src", "assets", "3rdPartyLicenses.json");
|
||||
const OUTPUT_FILE = path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"src",
|
||||
"assets",
|
||||
"3rdPartyLicenses.json",
|
||||
);
|
||||
const PACKAGE_JSON = path.join(__dirname, "..", "package.json");
|
||||
|
||||
// Ensure the output directory exists
|
||||
@@ -30,7 +41,9 @@ console.log("🔍 Generating frontend license report...");
|
||||
try {
|
||||
// Safety guard: don't run this script on fork PRs (workflow setzt PR_IS_FORK)
|
||||
if (process.env.PR_IS_FORK === "true" && !POSTPROCESS_ONLY) {
|
||||
console.error("Fork PR detected: only --input (postprocess-only) mode is allowed.");
|
||||
console.error(
|
||||
"Fork PR detected: only --input (postprocess-only) mode is allowed.",
|
||||
);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
@@ -90,13 +103,21 @@ try {
|
||||
licenseType = "Unknown";
|
||||
}
|
||||
|
||||
if ("posthog-js" === dep.name && licenseType.startsWith("SEE LICENSE IN LICENSE")) {
|
||||
licenseType = "SEE LICENSE IN LICENSE https://github.com/PostHog/posthog-js/blob/main/LICENSE";
|
||||
if (
|
||||
"posthog-js" === dep.name &&
|
||||
licenseType.startsWith("SEE LICENSE IN LICENSE")
|
||||
) {
|
||||
licenseType =
|
||||
"SEE LICENSE IN LICENSE https://github.com/PostHog/posthog-js/blob/main/LICENSE";
|
||||
}
|
||||
|
||||
return {
|
||||
name: dep.name,
|
||||
version: dep.installedVersion || dep.definedVersion || dep.remoteVersion || "unknown",
|
||||
version:
|
||||
dep.installedVersion ||
|
||||
dep.definedVersion ||
|
||||
dep.remoteVersion ||
|
||||
"unknown",
|
||||
licenseType: licenseType,
|
||||
repository: dep.link,
|
||||
url: dep.link,
|
||||
@@ -107,12 +128,17 @@ try {
|
||||
// Transform to match Java backend format
|
||||
const transformedData = {
|
||||
dependencies: licenseArray.map((dep) => {
|
||||
const licenseType = Array.isArray(dep.licenseType) ? dep.licenseType.join(", ") : dep.licenseType || "Unknown";
|
||||
const licenseType = Array.isArray(dep.licenseType)
|
||||
? dep.licenseType.join(", ")
|
||||
: dep.licenseType || "Unknown";
|
||||
const licenseUrl = dep.link || getLicenseUrl(licenseType);
|
||||
|
||||
return {
|
||||
moduleName: dep.name,
|
||||
moduleUrl: dep.repository || dep.url || `https://www.npmjs.com/package/${dep.name}`,
|
||||
moduleUrl:
|
||||
dep.repository ||
|
||||
dep.url ||
|
||||
`https://www.npmjs.com/package/${dep.name}`,
|
||||
moduleVersion: dep.version,
|
||||
moduleLicense: licenseType,
|
||||
moduleLicenseUrl: licenseUrl,
|
||||
@@ -122,7 +148,9 @@ try {
|
||||
|
||||
// Log summary of license types found
|
||||
const licenseSummary = licenseArray.reduce((acc, dep) => {
|
||||
const license = Array.isArray(dep.licenseType) ? dep.licenseType.join(", ") : dep.licenseType || "Unknown";
|
||||
const license = Array.isArray(dep.licenseType)
|
||||
? dep.licenseType.join(", ")
|
||||
: dep.licenseType || "Unknown";
|
||||
acc[license] = (acc[license] || 0) + 1;
|
||||
return acc;
|
||||
}, {});
|
||||
@@ -150,7 +178,10 @@ try {
|
||||
}
|
||||
|
||||
// Check for potentially problematic licenses
|
||||
const problematicLicenses = checkLicenseCompatibility(licenseSummary, licenseArray);
|
||||
const problematicLicenses = checkLicenseCompatibility(
|
||||
licenseSummary,
|
||||
licenseArray,
|
||||
);
|
||||
if (problematicLicenses.length > 0) {
|
||||
console.log("\n⚠️ License compatibility warnings:");
|
||||
problematicLicenses.forEach((warning) => {
|
||||
@@ -158,7 +189,13 @@ try {
|
||||
});
|
||||
|
||||
// Write license warnings to a separate file for CI/CD
|
||||
const warningsFile = path.join(__dirname, "..", "src", "assets", "license-warnings.json");
|
||||
const warningsFile = path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"src",
|
||||
"assets",
|
||||
"license-warnings.json",
|
||||
);
|
||||
writeFileSync(
|
||||
warningsFile,
|
||||
JSON.stringify(
|
||||
@@ -257,15 +294,21 @@ function checkLicenseCompatibility(licenseSummary, licenseArray) {
|
||||
// Copyleft licenses
|
||||
"GPL-2.0": "Strong copyleft license - requires derivative works to be GPL",
|
||||
"GPL-3.0": "Strong copyleft license - requires derivative works to be GPL",
|
||||
"LGPL-2.1": "Weak copyleft license - may require source disclosure for modifications",
|
||||
"LGPL-3.0": "Weak copyleft license - may require source disclosure for modifications",
|
||||
"AGPL-3.0": "Network copyleft license - requires source disclosure for network use",
|
||||
"AGPL-1.0": "Network copyleft license - requires source disclosure for network use",
|
||||
"LGPL-2.1":
|
||||
"Weak copyleft license - may require source disclosure for modifications",
|
||||
"LGPL-3.0":
|
||||
"Weak copyleft license - may require source disclosure for modifications",
|
||||
"AGPL-3.0":
|
||||
"Network copyleft license - requires source disclosure for network use",
|
||||
"AGPL-1.0":
|
||||
"Network copyleft license - requires source disclosure for network use",
|
||||
|
||||
// Other potentially problematic licenses
|
||||
WTFPL: "Potentially problematic license - legal uncertainty",
|
||||
"CC-BY-SA-4.0": "ShareAlike license - requires derivative works to use same license",
|
||||
"CC-BY-SA-3.0": "ShareAlike license - requires derivative works to use same license",
|
||||
"CC-BY-SA-4.0":
|
||||
"ShareAlike license - requires derivative works to use same license",
|
||||
"CC-BY-SA-3.0":
|
||||
"ShareAlike license - requires derivative works to use same license",
|
||||
"CC-BY-NC-4.0": "Non-commercial license - prohibits commercial use",
|
||||
"CC-BY-NC-3.0": "Non-commercial license - prohibits commercial use",
|
||||
"OSL-3.0": "Copyleft license - requires derivative works to be OSL",
|
||||
@@ -323,7 +366,9 @@ function checkLicenseCompatibility(licenseSummary, licenseArray) {
|
||||
|
||||
// Check if this license only affects our own packages
|
||||
const affectedPackages = licenseArray.filter((dep) => {
|
||||
const depLicense = Array.isArray(dep.licenseType) ? dep.licenseType.join(", ") : dep.licenseType;
|
||||
const depLicense = Array.isArray(dep.licenseType)
|
||||
? dep.licenseType.join(", ")
|
||||
: dep.licenseType;
|
||||
return depLicense === license;
|
||||
});
|
||||
|
||||
@@ -335,7 +380,10 @@ function checkLicenseCompatibility(licenseSummary, licenseArray) {
|
||||
dep.name.toLowerCase().includes("stirlingpdf"),
|
||||
);
|
||||
|
||||
if (isOnlyOurPackages && (license === "UNLICENSED" || license.startsWith("SEE LICENSE IN"))) {
|
||||
if (
|
||||
isOnlyOurPackages &&
|
||||
(license === "UNLICENSED" || license.startsWith("SEE LICENSE IN"))
|
||||
) {
|
||||
return; // Skip warnings for our own Stirling-PDF packages
|
||||
}
|
||||
|
||||
@@ -361,18 +409,25 @@ function checkLicenseCompatibility(licenseSummary, licenseArray) {
|
||||
}
|
||||
|
||||
// For AND licenses or OR licenses with no good options, check for problematic components
|
||||
const hasProblematicComponent = Object.keys(problematicLicenses).some((problematic) => license.includes(problematic));
|
||||
const hasProblematicComponent = Object.keys(problematicLicenses).some(
|
||||
(problematic) => license.includes(problematic),
|
||||
);
|
||||
|
||||
if (hasProblematicComponent) {
|
||||
const affectedPackages = licenseArray
|
||||
.filter((dep) => {
|
||||
const depLicense = Array.isArray(dep.licenseType) ? dep.licenseType.join(", ") : dep.licenseType;
|
||||
const depLicense = Array.isArray(dep.licenseType)
|
||||
? dep.licenseType.join(", ")
|
||||
: dep.licenseType;
|
||||
return depLicense === license;
|
||||
})
|
||||
.map((dep) => ({
|
||||
name: dep.name,
|
||||
version: dep.version,
|
||||
url: dep.repository || dep.url || `https://www.npmjs.com/package/${dep.name}`,
|
||||
url:
|
||||
dep.repository ||
|
||||
dep.url ||
|
||||
`https://www.npmjs.com/package/${dep.name}`,
|
||||
}));
|
||||
|
||||
const licenseType = license.includes("AND") ? "AND" : "OR";
|
||||
@@ -397,21 +452,30 @@ function checkLicenseCompatibility(licenseSummary, licenseArray) {
|
||||
if (problematicLicenses[license]) {
|
||||
const affectedPackages = licenseArray
|
||||
.filter((dep) => {
|
||||
const depLicense = Array.isArray(dep.licenseType) ? dep.licenseType.join(", ") : dep.licenseType;
|
||||
const depLicense = Array.isArray(dep.licenseType)
|
||||
? dep.licenseType.join(", ")
|
||||
: dep.licenseType;
|
||||
return depLicense === license;
|
||||
})
|
||||
.map((dep) => ({
|
||||
name: dep.name,
|
||||
version: dep.version,
|
||||
url: dep.repository || dep.url || `https://www.npmjs.com/package/${dep.name}`,
|
||||
url:
|
||||
dep.repository ||
|
||||
dep.url ||
|
||||
`https://www.npmjs.com/package/${dep.name}`,
|
||||
}));
|
||||
|
||||
const packageList =
|
||||
affectedPackages
|
||||
.map((pkg) => pkg.name)
|
||||
.slice(0, 5)
|
||||
.join(", ") + (affectedPackages.length > 5 ? `, and ${affectedPackages.length - 5} more` : "");
|
||||
const licenseUrl = getLicenseUrl(license) || "https://opensource.org/licenses";
|
||||
.join(", ") +
|
||||
(affectedPackages.length > 5
|
||||
? `, and ${affectedPackages.length - 5} more`
|
||||
: "");
|
||||
const licenseUrl =
|
||||
getLicenseUrl(license) || "https://opensource.org/licenses";
|
||||
|
||||
warnings.push({
|
||||
message: `⚠️ This PR contains ${count} package${count > 1 ? "s" : ""} with license type [${license}](${licenseUrl}) - ${problematicLicenses[license]}. Affected packages: ${packageList}`,
|
||||
@@ -425,13 +489,18 @@ function checkLicenseCompatibility(licenseSummary, licenseArray) {
|
||||
// Unknown license type - flag for manual review
|
||||
const affectedPackages = licenseArray
|
||||
.filter((dep) => {
|
||||
const depLicense = Array.isArray(dep.licenseType) ? dep.licenseType.join(", ") : dep.licenseType;
|
||||
const depLicense = Array.isArray(dep.licenseType)
|
||||
? dep.licenseType.join(", ")
|
||||
: dep.licenseType;
|
||||
return depLicense === license;
|
||||
})
|
||||
.map((dep) => ({
|
||||
name: dep.name,
|
||||
version: dep.version,
|
||||
url: dep.repository || dep.url || `https://www.npmjs.com/package/${dep.name}`,
|
||||
url:
|
||||
dep.repository ||
|
||||
dep.url ||
|
||||
`https://www.npmjs.com/package/${dep.name}`,
|
||||
}));
|
||||
|
||||
warnings.push({
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
|
||||
/* Font Stack */
|
||||
--font-family:
|
||||
-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
|
||||
"Helvetica Neue", sans-serif;
|
||||
-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu",
|
||||
"Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
||||
}
|
||||
|
||||
* {
|
||||
|
||||
@@ -10,15 +10,39 @@
|
||||
<!-- Page 1: Hero / Cover Page -->
|
||||
<div class="page page-1">
|
||||
<div class="decorative-shapes">
|
||||
<img src="../../public/modern-logo/StirlingPDFLogoNoTextLight.svg" class="shape shape-1" alt="" />
|
||||
<img src="../../public/modern-logo/StirlingPDFLogoNoTextDark.svg" class="shape shape-2" alt="" />
|
||||
<img src="../../public/modern-logo/StirlingPDFLogoNoTextLight.svg" class="shape shape-3" alt="" />
|
||||
<img src="../../public/modern-logo/StirlingPDFLogoNoTextDark.svg" class="shape shape-4" alt="" />
|
||||
<img src="../../public/modern-logo/StirlingPDFLogoNoTextLight.svg" class="shape shape-5" alt="" />
|
||||
<img
|
||||
src="../../public/modern-logo/StirlingPDFLogoNoTextLight.svg"
|
||||
class="shape shape-1"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
src="../../public/modern-logo/StirlingPDFLogoNoTextDark.svg"
|
||||
class="shape shape-2"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
src="../../public/modern-logo/StirlingPDFLogoNoTextLight.svg"
|
||||
class="shape shape-3"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
src="../../public/modern-logo/StirlingPDFLogoNoTextDark.svg"
|
||||
class="shape shape-4"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
src="../../public/modern-logo/StirlingPDFLogoNoTextLight.svg"
|
||||
class="shape shape-5"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div class="hero-content">
|
||||
<div class="logo-container">
|
||||
<img src="../../public/modern-logo/StirlingPDFLogoWhiteText.svg" alt="Stirling PDF" class="hero-logo" />
|
||||
<img
|
||||
src="../../public/modern-logo/StirlingPDFLogoWhiteText.svg"
|
||||
alt="Stirling PDF"
|
||||
class="hero-logo"
|
||||
/>
|
||||
</div>
|
||||
<h1 class="hero-tagline">The Free Adobe Acrobat Alternative</h1>
|
||||
<div class="hero-stats">
|
||||
@@ -40,21 +64,31 @@
|
||||
<div class="content-wrapper">
|
||||
<h2 class="page-title">What is Stirling PDF?</h2>
|
||||
<p class="intro-text">
|
||||
Stirling PDF is a robust, web-based PDF manipulation tool. It enables you to carry out various operations on PDF
|
||||
files, including splitting, merging, converting, rearranging, adding images, rotating, compressing, and more.
|
||||
Stirling PDF is a robust, web-based PDF manipulation tool. It enables
|
||||
you to carry out various operations on PDF files, including splitting,
|
||||
merging, converting, rearranging, adding images, rotating,
|
||||
compressing, and more.
|
||||
</p>
|
||||
|
||||
<div class="value-props">
|
||||
<div class="value-prop">
|
||||
<div class="value-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3>50+ PDF Operations</h3>
|
||||
<p>Comprehensive toolkit covering all your PDF needs. From basic operations to advanced processing.</p>
|
||||
<p>
|
||||
Comprehensive toolkit covering all your PDF needs. From basic
|
||||
operations to advanced processing.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="value-prop">
|
||||
@@ -66,24 +100,42 @@
|
||||
</svg>
|
||||
</div>
|
||||
<h3>Workflow Automation</h3>
|
||||
<p>Chain multiple operations together and save them as reusable workflows. Perfect for recurring tasks.</p>
|
||||
<p>
|
||||
Chain multiple operations together and save them as reusable
|
||||
workflows. Perfect for recurring tasks.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="value-prop">
|
||||
<div class="value-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<line x1="2" y1="12" x2="22" y2="12" />
|
||||
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
|
||||
<path
|
||||
d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3>Multi-Language Support</h3>
|
||||
<p>Available in over 30 languages with community-contributed translations. Accessible to users worldwide.</p>
|
||||
<p>
|
||||
Available in over 30 languages with community-contributed
|
||||
translations. Accessible to users worldwide.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="value-prop">
|
||||
<div class="value-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<rect x="5" y="11" width="14" height="10" rx="2" />
|
||||
<circle cx="12" cy="16" r="1" />
|
||||
<path d="M8 11V7a4 4 0 0 1 8 0v4" />
|
||||
@@ -91,30 +143,47 @@
|
||||
</div>
|
||||
<h3>Privacy First</h3>
|
||||
<p>
|
||||
Self-hosted solution means your data stays on your infrastructure. You have full control over your documents.
|
||||
Self-hosted solution means your data stays on your infrastructure.
|
||||
You have full control over your documents.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="value-prop">
|
||||
<div class="value-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="m9 12 2 2 4-4" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3>Open Source</h3>
|
||||
<p>Transparent, community-driven development. Inspect the code, contribute features, and adapt as needed.</p>
|
||||
<p>
|
||||
Transparent, community-driven development. Inspect the code,
|
||||
contribute features, and adapt as needed.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="value-prop">
|
||||
<div class="value-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<polyline points="16 18 22 12 16 6" />
|
||||
<polyline points="8 6 2 12 8 18" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3>API Access</h3>
|
||||
<p>RESTful API for integration with external tools and scripts. Automate PDF operations programmatically.</p>
|
||||
<p>
|
||||
RESTful API for integration with external tools and scripts.
|
||||
Automate PDF operations programmatically.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -129,8 +198,15 @@
|
||||
<div class="feature-card" data-category="general">
|
||||
<div class="feature-header">
|
||||
<div class="feature-icon-large">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path
|
||||
d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"
|
||||
/>
|
||||
<polyline points="14 2 14 8 20 8" />
|
||||
<line x1="16" y1="13" x2="8" y2="13" />
|
||||
<line x1="16" y1="17" x2="8" y2="17" />
|
||||
@@ -151,7 +227,12 @@
|
||||
<div class="feature-card" data-category="security">
|
||||
<div class="feature-header">
|
||||
<div class="feature-icon-large">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
||||
</svg>
|
||||
|
||||
@@ -20,7 +20,9 @@ const args = process.argv.slice(2);
|
||||
const isDesktop = args.includes("--desktop");
|
||||
const isSaas = args.includes("--saas");
|
||||
|
||||
console.log("setup-env: see frontend/README.md#environment-variables for documentation");
|
||||
console.log(
|
||||
"setup-env: see frontend/README.md#environment-variables for documentation",
|
||||
);
|
||||
|
||||
function getExampleKeys(exampleFile: string): string[] {
|
||||
const examplePath = join(root, exampleFile);
|
||||
@@ -44,7 +46,9 @@ function ensureEnvFile(envFile: string, exampleFile: string): boolean {
|
||||
|
||||
config({ path: envPath });
|
||||
|
||||
const missing = getExampleKeys(exampleFile).filter((k) => !(k in process.env));
|
||||
const missing = getExampleKeys(exampleFile).filter(
|
||||
(k) => !(k in process.env),
|
||||
);
|
||||
|
||||
if (missing.length > 0) {
|
||||
console.error(
|
||||
@@ -62,7 +66,8 @@ let failed = false;
|
||||
failed = ensureEnvFile(".env", "config/.env.example") || failed;
|
||||
|
||||
if (isDesktop) {
|
||||
failed = ensureEnvFile(".env.desktop", "config/.env.desktop.example") || failed;
|
||||
failed =
|
||||
ensureEnvFile(".env.desktop", "config/.env.desktop.example") || failed;
|
||||
}
|
||||
|
||||
if (isSaas) {
|
||||
@@ -75,7 +80,9 @@ const allExampleKeys = new Set([
|
||||
...getExampleKeys("config/.env.desktop.example"),
|
||||
...getExampleKeys("config/.env.saas.example"),
|
||||
]);
|
||||
const unknownViteVars = Object.keys(process.env).filter((k) => k.startsWith("VITE_") && !allExampleKeys.has(k));
|
||||
const unknownViteVars = Object.keys(process.env).filter(
|
||||
(k) => k.startsWith("VITE_") && !allExampleKeys.has(k),
|
||||
);
|
||||
if (unknownViteVars.length > 0) {
|
||||
console.warn(
|
||||
"setup-env: the following VITE_ vars are set but not listed in any example file:\n" +
|
||||
|
||||
Reference in New Issue
Block a user