@julien-lin/universal-pwa-core 1.3.4 → 1.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +281 -78
- package/dist/index.d.cts +46 -1
- package/dist/index.d.ts +46 -1
- package/dist/index.js +265 -63
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -60,6 +60,7 @@ __export(index_exports, {
|
|
|
60
60
|
generateSplashScreensOnly: () => generateSplashScreensOnly,
|
|
61
61
|
injectMetaTags: () => injectMetaTags,
|
|
62
62
|
injectMetaTagsInFile: () => injectMetaTagsInFile,
|
|
63
|
+
injectMetaTagsInFilesBatch: () => injectMetaTagsInFilesBatch,
|
|
63
64
|
optimizeImage: () => optimizeImage,
|
|
64
65
|
optimizeProject: () => optimizeProject,
|
|
65
66
|
optimizeProjectImages: () => optimizeProjectImages,
|
|
@@ -1646,9 +1647,11 @@ async function scanProject(options) {
|
|
|
1646
1647
|
buildTool: null
|
|
1647
1648
|
}
|
|
1648
1649
|
};
|
|
1649
|
-
const assetsCandidate =
|
|
1650
|
+
const [assetsCandidate, architectureCandidate] = await Promise.all([
|
|
1651
|
+
includeAssets ? detectAssets(projectPath) : Promise.resolve(getEmptyAssets()),
|
|
1652
|
+
includeArchitecture ? detectArchitecture(projectPath) : Promise.resolve(getEmptyArchitecture())
|
|
1653
|
+
]);
|
|
1650
1654
|
const assets = isAssetDetectionResult(assetsCandidate) ? assetsCandidate : getEmptyAssets();
|
|
1651
|
-
const architectureCandidate = includeArchitecture ? await detectArchitecture(projectPath) : getEmptyArchitecture();
|
|
1652
1655
|
const architecture = isArchitectureDetectionResult(architectureCandidate) ? architectureCandidate : getEmptyArchitecture();
|
|
1653
1656
|
const result = {
|
|
1654
1657
|
framework,
|
|
@@ -1814,9 +1817,117 @@ function generateAndWriteManifest(options, outputDir) {
|
|
|
1814
1817
|
}
|
|
1815
1818
|
|
|
1816
1819
|
// src/generator/icon-generator.ts
|
|
1820
|
+
var import_sharp3 = __toESM(require("sharp"), 1);
|
|
1821
|
+
var import_fs9 = require("fs");
|
|
1822
|
+
var import_path8 = require("path");
|
|
1823
|
+
|
|
1824
|
+
// src/validator/icon-validator.ts
|
|
1817
1825
|
var import_sharp2 = __toESM(require("sharp"), 1);
|
|
1818
1826
|
var import_fs8 = require("fs");
|
|
1819
|
-
|
|
1827
|
+
async function validateIconSource(options) {
|
|
1828
|
+
const {
|
|
1829
|
+
sourceImage,
|
|
1830
|
+
strict = false,
|
|
1831
|
+
minRecommendedSize = 192,
|
|
1832
|
+
optimalSize = 512
|
|
1833
|
+
} = options;
|
|
1834
|
+
const result = {
|
|
1835
|
+
valid: true,
|
|
1836
|
+
errors: [],
|
|
1837
|
+
warnings: [],
|
|
1838
|
+
suggestions: []
|
|
1839
|
+
};
|
|
1840
|
+
if (!(0, import_fs8.existsSync)(sourceImage)) {
|
|
1841
|
+
result.valid = false;
|
|
1842
|
+
result.errors.push(`Source image not found: ${sourceImage}`);
|
|
1843
|
+
return result;
|
|
1844
|
+
}
|
|
1845
|
+
try {
|
|
1846
|
+
const image = (0, import_sharp2.default)(sourceImage);
|
|
1847
|
+
const metadata = await image.metadata();
|
|
1848
|
+
if (!metadata.width || !metadata.height) {
|
|
1849
|
+
result.valid = false;
|
|
1850
|
+
result.errors.push("Unable to read image dimensions");
|
|
1851
|
+
return result;
|
|
1852
|
+
}
|
|
1853
|
+
const { width, height, format, size } = metadata;
|
|
1854
|
+
const minDimension = Math.min(width, height);
|
|
1855
|
+
const maxDimension = Math.max(width, height);
|
|
1856
|
+
const isSquare = width === height;
|
|
1857
|
+
result.metadata = {
|
|
1858
|
+
width,
|
|
1859
|
+
height,
|
|
1860
|
+
format: format || "unknown",
|
|
1861
|
+
size: size || 0
|
|
1862
|
+
};
|
|
1863
|
+
const supportedFormats = ["png", "jpeg", "jpg", "webp", "svg"];
|
|
1864
|
+
if (!format || !supportedFormats.includes(format.toLowerCase())) {
|
|
1865
|
+
result.valid = false;
|
|
1866
|
+
result.errors.push(
|
|
1867
|
+
`Unsupported image format: ${format || "unknown"}. Supported formats: ${supportedFormats.join(", ")}`
|
|
1868
|
+
);
|
|
1869
|
+
}
|
|
1870
|
+
if (minDimension < minRecommendedSize) {
|
|
1871
|
+
const message = `Icon dimensions too small: ${width}x${height}. Minimum recommended: ${minRecommendedSize}x${minRecommendedSize}`;
|
|
1872
|
+
if (strict) {
|
|
1873
|
+
result.valid = false;
|
|
1874
|
+
result.errors.push(message);
|
|
1875
|
+
} else {
|
|
1876
|
+
result.warnings.push(message);
|
|
1877
|
+
result.suggestions.push(
|
|
1878
|
+
`Use an image at least ${minRecommendedSize}x${minRecommendedSize} pixels for best PWA compatibility`
|
|
1879
|
+
);
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
if (minDimension < optimalSize) {
|
|
1883
|
+
result.warnings.push(
|
|
1884
|
+
`Icon dimensions below optimal size: ${width}x${height}. Optimal size: ${optimalSize}x${optimalSize} for best quality on all devices`
|
|
1885
|
+
);
|
|
1886
|
+
result.suggestions.push(
|
|
1887
|
+
`Consider using a ${optimalSize}x${optimalSize} source image for optimal icon quality`
|
|
1888
|
+
);
|
|
1889
|
+
}
|
|
1890
|
+
if (!isSquare) {
|
|
1891
|
+
result.warnings.push(
|
|
1892
|
+
`Icon is not square (${width}x${height}). Square images work best for PWA icons`
|
|
1893
|
+
);
|
|
1894
|
+
result.suggestions.push(
|
|
1895
|
+
"Consider using a square source image. The icon will be cropped to fit during generation"
|
|
1896
|
+
);
|
|
1897
|
+
}
|
|
1898
|
+
if (format?.toLowerCase() === "jpg" || format?.toLowerCase() === "jpeg") {
|
|
1899
|
+
result.suggestions.push(
|
|
1900
|
+
"PNG format is recommended for icons with transparency. Consider converting JPG to PNG"
|
|
1901
|
+
);
|
|
1902
|
+
}
|
|
1903
|
+
if (size && size > 1024 * 1024) {
|
|
1904
|
+
const sizeMB = (size / (1024 * 1024)).toFixed(2);
|
|
1905
|
+
result.warnings.push(`Icon file size is large: ${sizeMB}MB. This may slow down generation`);
|
|
1906
|
+
result.suggestions.push(
|
|
1907
|
+
"Consider optimizing the source image before generation to reduce file size"
|
|
1908
|
+
);
|
|
1909
|
+
} else if (size && size > 500 * 1024) {
|
|
1910
|
+
const sizeKB = (size / 1024).toFixed(2);
|
|
1911
|
+
result.suggestions.push(
|
|
1912
|
+
`Icon file size is ${sizeKB}KB. Consider optimizing to reduce generation time`
|
|
1913
|
+
);
|
|
1914
|
+
}
|
|
1915
|
+
const aspectRatio = maxDimension / minDimension;
|
|
1916
|
+
if (aspectRatio > 2) {
|
|
1917
|
+
result.warnings.push(
|
|
1918
|
+
`Icon has extreme aspect ratio (${aspectRatio.toFixed(2)}:1). Square images are recommended`
|
|
1919
|
+
);
|
|
1920
|
+
}
|
|
1921
|
+
return result;
|
|
1922
|
+
} catch (error) {
|
|
1923
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1924
|
+
result.valid = false;
|
|
1925
|
+
result.errors.push(`Failed to validate icon: ${message}`);
|
|
1926
|
+
return result;
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
// src/generator/icon-generator.ts
|
|
1820
1931
|
var STANDARD_ICON_SIZES = [
|
|
1821
1932
|
{ width: 72, height: 72, name: "icon-72x72.png" },
|
|
1822
1933
|
{ width: 96, height: 96, name: "icon-96x96.png" },
|
|
@@ -1850,67 +1961,114 @@ async function generateIcons(options) {
|
|
|
1850
1961
|
iconSizes = STANDARD_ICON_SIZES,
|
|
1851
1962
|
splashSizes = STANDARD_SPLASH_SIZES,
|
|
1852
1963
|
format = "png",
|
|
1853
|
-
quality = 90
|
|
1964
|
+
quality = 90,
|
|
1965
|
+
validate = false,
|
|
1966
|
+
strictValidation = false
|
|
1854
1967
|
} = options;
|
|
1855
|
-
if (!(0,
|
|
1968
|
+
if (!(0, import_fs9.existsSync)(sourceImage)) {
|
|
1856
1969
|
throw new Error(`Source image not found: ${sourceImage}`);
|
|
1857
1970
|
}
|
|
1858
|
-
|
|
1971
|
+
let validation;
|
|
1972
|
+
if (validate) {
|
|
1973
|
+
validation = await validateIconSource({
|
|
1974
|
+
sourceImage,
|
|
1975
|
+
strict: strictValidation
|
|
1976
|
+
});
|
|
1977
|
+
if (strictValidation && !validation.valid) {
|
|
1978
|
+
const errorMessages = validation.errors.join("; ");
|
|
1979
|
+
throw new Error(`Icon validation failed: ${errorMessages}`);
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
(0, import_fs9.mkdirSync)(outputDir, { recursive: true });
|
|
1859
1983
|
const generatedFiles = [];
|
|
1860
1984
|
const icons = [];
|
|
1861
1985
|
const splashScreens = [];
|
|
1862
|
-
const image = (0,
|
|
1986
|
+
const image = (0, import_sharp3.default)(sourceImage);
|
|
1863
1987
|
const metadata = await image.metadata();
|
|
1864
1988
|
if (!metadata.width || !metadata.height) {
|
|
1865
1989
|
throw new Error("Unable to read image dimensions");
|
|
1866
1990
|
}
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1991
|
+
const iconResults = await Promise.all(
|
|
1992
|
+
iconSizes.map(async (size) => {
|
|
1993
|
+
const outputPath = (0, import_path8.join)(outputDir, size.name);
|
|
1994
|
+
try {
|
|
1995
|
+
let pipeline = image.clone().resize(size.width, size.height, {
|
|
1996
|
+
fit: "cover",
|
|
1997
|
+
position: "center"
|
|
1998
|
+
});
|
|
1999
|
+
if (format === "png") {
|
|
2000
|
+
pipeline = pipeline.png({ quality, compressionLevel: 9 });
|
|
2001
|
+
} else {
|
|
2002
|
+
pipeline = pipeline.webp({ quality });
|
|
2003
|
+
}
|
|
2004
|
+
await pipeline.toFile(outputPath);
|
|
2005
|
+
return {
|
|
2006
|
+
success: true,
|
|
2007
|
+
file: outputPath,
|
|
2008
|
+
icon: {
|
|
2009
|
+
src: `/${size.name}`,
|
|
2010
|
+
sizes: `${size.width}x${size.height}`,
|
|
2011
|
+
type: format === "png" ? "image/png" : "image/webp",
|
|
2012
|
+
purpose: size.width >= 192 && size.width <= 512 ? "any" : void 0
|
|
2013
|
+
}
|
|
2014
|
+
};
|
|
2015
|
+
} catch (err) {
|
|
2016
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2017
|
+
return {
|
|
2018
|
+
success: false,
|
|
2019
|
+
error: `Failed to generate icon ${size.name}: ${message}`,
|
|
2020
|
+
size: size.name
|
|
2021
|
+
};
|
|
1878
2022
|
}
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
} catch (err) {
|
|
1888
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1889
|
-
throw new Error(`Failed to generate icon ${size.name}: ${message}`);
|
|
2023
|
+
})
|
|
2024
|
+
);
|
|
2025
|
+
for (const result of iconResults) {
|
|
2026
|
+
if (result.success) {
|
|
2027
|
+
generatedFiles.push(result.file);
|
|
2028
|
+
icons.push(result.icon);
|
|
2029
|
+
} else {
|
|
2030
|
+
throw new Error(result.error);
|
|
1890
2031
|
}
|
|
1891
2032
|
}
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
2033
|
+
const splashResults = await Promise.all(
|
|
2034
|
+
splashSizes.map(async (size) => {
|
|
2035
|
+
const outputPath = (0, import_path8.join)(outputDir, size.name);
|
|
2036
|
+
try {
|
|
2037
|
+
let pipeline = image.clone().resize(size.width, size.height, {
|
|
2038
|
+
fit: "cover",
|
|
2039
|
+
position: "center"
|
|
2040
|
+
});
|
|
2041
|
+
if (format === "png") {
|
|
2042
|
+
pipeline = pipeline.png({ quality, compressionLevel: 9 });
|
|
2043
|
+
} else {
|
|
2044
|
+
pipeline = pipeline.webp({ quality });
|
|
2045
|
+
}
|
|
2046
|
+
await pipeline.toFile(outputPath);
|
|
2047
|
+
return {
|
|
2048
|
+
success: true,
|
|
2049
|
+
file: outputPath,
|
|
2050
|
+
splash: {
|
|
2051
|
+
src: `/${size.name}`,
|
|
2052
|
+
sizes: `${size.width}x${size.height}`,
|
|
2053
|
+
type: format === "png" ? "image/png" : "image/webp"
|
|
2054
|
+
}
|
|
2055
|
+
};
|
|
2056
|
+
} catch (err) {
|
|
2057
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2058
|
+
return {
|
|
2059
|
+
success: false,
|
|
2060
|
+
error: `Failed to generate splash screen ${size.name}: ${message}`,
|
|
2061
|
+
size: size.name
|
|
2062
|
+
};
|
|
1903
2063
|
}
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1913
|
-
throw new Error(`Failed to generate splash screen ${size.name}: ${message}`);
|
|
2064
|
+
})
|
|
2065
|
+
);
|
|
2066
|
+
for (const result of splashResults) {
|
|
2067
|
+
if (result.success) {
|
|
2068
|
+
generatedFiles.push(result.file);
|
|
2069
|
+
splashScreens.push(result.splash);
|
|
2070
|
+
} else {
|
|
2071
|
+
throw new Error(result.error);
|
|
1914
2072
|
}
|
|
1915
2073
|
}
|
|
1916
2074
|
if (format === "png") {
|
|
@@ -1929,7 +2087,8 @@ async function generateIcons(options) {
|
|
|
1929
2087
|
return {
|
|
1930
2088
|
icons,
|
|
1931
2089
|
splashScreens,
|
|
1932
|
-
generatedFiles
|
|
2090
|
+
generatedFiles,
|
|
2091
|
+
validation
|
|
1933
2092
|
};
|
|
1934
2093
|
}
|
|
1935
2094
|
async function generateIconsOnly(options) {
|
|
@@ -1953,13 +2112,13 @@ async function generateSplashScreensOnly(options) {
|
|
|
1953
2112
|
};
|
|
1954
2113
|
}
|
|
1955
2114
|
async function generateFavicon(sourceImage, outputDir) {
|
|
1956
|
-
if (!(0,
|
|
2115
|
+
if (!(0, import_fs9.existsSync)(sourceImage)) {
|
|
1957
2116
|
throw new Error(`Source image not found: ${sourceImage}`);
|
|
1958
2117
|
}
|
|
1959
|
-
(0,
|
|
2118
|
+
(0, import_fs9.mkdirSync)(outputDir, { recursive: true });
|
|
1960
2119
|
const faviconPath = (0, import_path8.join)(outputDir, "favicon.ico");
|
|
1961
2120
|
try {
|
|
1962
|
-
await (0,
|
|
2121
|
+
await (0, import_sharp3.default)(sourceImage).resize(32, 32, {
|
|
1963
2122
|
fit: "cover",
|
|
1964
2123
|
position: "center"
|
|
1965
2124
|
}).png().toFile(faviconPath);
|
|
@@ -1970,13 +2129,13 @@ async function generateFavicon(sourceImage, outputDir) {
|
|
|
1970
2129
|
}
|
|
1971
2130
|
}
|
|
1972
2131
|
async function generateAppleTouchIcon(sourceImage, outputDir) {
|
|
1973
|
-
if (!(0,
|
|
2132
|
+
if (!(0, import_fs9.existsSync)(sourceImage)) {
|
|
1974
2133
|
throw new Error(`Source image not found: ${sourceImage}`);
|
|
1975
2134
|
}
|
|
1976
|
-
(0,
|
|
2135
|
+
(0, import_fs9.mkdirSync)(outputDir, { recursive: true });
|
|
1977
2136
|
const appleIconPath = (0, import_path8.join)(outputDir, "apple-touch-icon.png");
|
|
1978
2137
|
try {
|
|
1979
|
-
await (0,
|
|
2138
|
+
await (0, import_sharp3.default)(sourceImage).resize(180, 180, {
|
|
1980
2139
|
fit: "cover",
|
|
1981
2140
|
position: "center"
|
|
1982
2141
|
}).png({ quality: 90, compressionLevel: 9 }).toFile(appleIconPath);
|
|
@@ -1989,7 +2148,7 @@ async function generateAppleTouchIcon(sourceImage, outputDir) {
|
|
|
1989
2148
|
|
|
1990
2149
|
// src/generator/service-worker-generator.ts
|
|
1991
2150
|
var import_workbox_build = require("workbox-build");
|
|
1992
|
-
var
|
|
2151
|
+
var import_fs10 = require("fs");
|
|
1993
2152
|
var import_path9 = require("path");
|
|
1994
2153
|
var import_universal_pwa_templates = require("@julien-lin/universal-pwa-templates");
|
|
1995
2154
|
async function generateServiceWorker(options) {
|
|
@@ -2004,11 +2163,11 @@ async function generateServiceWorker(options) {
|
|
|
2004
2163
|
swDest = "sw.js",
|
|
2005
2164
|
offlinePage
|
|
2006
2165
|
} = options;
|
|
2007
|
-
(0,
|
|
2166
|
+
(0, import_fs10.mkdirSync)(outputDir, { recursive: true });
|
|
2008
2167
|
const finalTemplateType = templateType ?? (0, import_universal_pwa_templates.determineTemplateType)(architecture, framework ?? null);
|
|
2009
2168
|
const template = (0, import_universal_pwa_templates.getServiceWorkerTemplate)(finalTemplateType);
|
|
2010
2169
|
const swSrcPath = (0, import_path9.join)(outputDir, "sw-src.js");
|
|
2011
|
-
(0,
|
|
2170
|
+
(0, import_fs10.writeFileSync)(swSrcPath, template.content, "utf-8");
|
|
2012
2171
|
const swDestPath = (0, import_path9.join)(outputDir, swDest);
|
|
2013
2172
|
const workboxConfig = {
|
|
2014
2173
|
globDirectory: globDirectory ?? projectPath,
|
|
@@ -2024,7 +2183,7 @@ async function generateServiceWorker(options) {
|
|
|
2024
2183
|
try {
|
|
2025
2184
|
const result = await (0, import_workbox_build.injectManifest)(workboxConfig);
|
|
2026
2185
|
try {
|
|
2027
|
-
if ((0,
|
|
2186
|
+
if ((0, import_fs10.existsSync)(swSrcPath)) {
|
|
2028
2187
|
}
|
|
2029
2188
|
} catch {
|
|
2030
2189
|
}
|
|
@@ -2054,7 +2213,7 @@ async function generateSimpleServiceWorker(options) {
|
|
|
2054
2213
|
clientsClaim = true,
|
|
2055
2214
|
runtimeCaching
|
|
2056
2215
|
} = options;
|
|
2057
|
-
(0,
|
|
2216
|
+
(0, import_fs10.mkdirSync)(outputDir, { recursive: true });
|
|
2058
2217
|
const swDestPath = (0, import_path9.join)(outputDir, swDest);
|
|
2059
2218
|
const workboxConfig = {
|
|
2060
2219
|
globDirectory: globDirectory ?? projectPath,
|
|
@@ -2109,7 +2268,7 @@ async function generateAndWriteServiceWorker(options) {
|
|
|
2109
2268
|
}
|
|
2110
2269
|
|
|
2111
2270
|
// src/generator/https-checker.ts
|
|
2112
|
-
var
|
|
2271
|
+
var import_fs11 = require("fs");
|
|
2113
2272
|
var import_path10 = require("path");
|
|
2114
2273
|
function checkHttps(url, allowHttpLocalhost = true) {
|
|
2115
2274
|
let parsedUrl;
|
|
@@ -2166,29 +2325,29 @@ function detectProjectUrl(projectPath) {
|
|
|
2166
2325
|
];
|
|
2167
2326
|
for (const config of configFiles) {
|
|
2168
2327
|
const filePath = (0, import_path10.join)(projectPath, config.file);
|
|
2169
|
-
if ((0,
|
|
2328
|
+
if ((0, import_fs11.existsSync)(filePath)) {
|
|
2170
2329
|
try {
|
|
2171
2330
|
if (config.file.endsWith(".json") && config.key) {
|
|
2172
|
-
const parsed = JSON.parse((0,
|
|
2331
|
+
const parsed = JSON.parse((0, import_fs11.readFileSync)(filePath, "utf-8"));
|
|
2173
2332
|
const content = parsed;
|
|
2174
2333
|
const value = content[config.key];
|
|
2175
2334
|
if (value && typeof value === "string") {
|
|
2176
2335
|
return value;
|
|
2177
2336
|
}
|
|
2178
2337
|
} else if (config.file.endsWith(".toml") && config.pattern) {
|
|
2179
|
-
const content = (0,
|
|
2338
|
+
const content = (0, import_fs11.readFileSync)(filePath, "utf-8");
|
|
2180
2339
|
const match = config.pattern ? content.match(config.pattern) : null;
|
|
2181
2340
|
if (match && match[1]) {
|
|
2182
2341
|
return match[1];
|
|
2183
2342
|
}
|
|
2184
2343
|
} else if ((config.file.endsWith(".js") || config.file.endsWith(".ts")) && config.pattern) {
|
|
2185
|
-
const content = (0,
|
|
2344
|
+
const content = (0, import_fs11.readFileSync)(filePath, "utf-8");
|
|
2186
2345
|
const match = config.pattern ? content.match(config.pattern) : null;
|
|
2187
2346
|
if (match && match[1]) {
|
|
2188
2347
|
return match[1];
|
|
2189
2348
|
}
|
|
2190
2349
|
} else if (config.file.startsWith(".env") && config.pattern) {
|
|
2191
|
-
const content = (0,
|
|
2350
|
+
const content = (0, import_fs11.readFileSync)(filePath, "utf-8");
|
|
2192
2351
|
const lines = content.split("\n");
|
|
2193
2352
|
for (const line of lines) {
|
|
2194
2353
|
const match = config.pattern ? line.match(config.pattern) : null;
|
|
@@ -2227,9 +2386,9 @@ function checkProjectHttps(options = {}) {
|
|
|
2227
2386
|
|
|
2228
2387
|
// src/injector/html-parser.ts
|
|
2229
2388
|
var import_htmlparser2 = require("htmlparser2");
|
|
2230
|
-
var
|
|
2389
|
+
var import_fs12 = require("fs");
|
|
2231
2390
|
function parseHTMLFile(filePath, options = {}) {
|
|
2232
|
-
const content = (0,
|
|
2391
|
+
const content = (0, import_fs12.readFileSync)(filePath, "utf-8");
|
|
2233
2392
|
return parseHTML(content, options);
|
|
2234
2393
|
}
|
|
2235
2394
|
function parseHTML(htmlContent, options = {}) {
|
|
@@ -2363,7 +2522,7 @@ function serializeHTML(parsed) {
|
|
|
2363
2522
|
}
|
|
2364
2523
|
|
|
2365
2524
|
// src/injector/meta-injector.ts
|
|
2366
|
-
var
|
|
2525
|
+
var import_fs13 = require("fs");
|
|
2367
2526
|
var import_dom_serializer = require("dom-serializer");
|
|
2368
2527
|
function injectMetaTags(htmlContent, options = {}) {
|
|
2369
2528
|
const parsed = parseHTML(htmlContent);
|
|
@@ -2732,18 +2891,61 @@ function escapeJavaScriptString(str) {
|
|
|
2732
2891
|
function injectMetaTagsInFile(filePath, options = {}) {
|
|
2733
2892
|
const parsed = parseHTMLFile(filePath);
|
|
2734
2893
|
const { html, result } = injectMetaTags(parsed.originalContent, options);
|
|
2735
|
-
(0,
|
|
2894
|
+
(0, import_fs13.writeFileSync)(filePath, html, "utf-8");
|
|
2736
2895
|
return result;
|
|
2737
2896
|
}
|
|
2897
|
+
async function injectMetaTagsInFilesBatch(batchOptions) {
|
|
2898
|
+
const { files, options, concurrency = 5, continueOnError = true } = batchOptions;
|
|
2899
|
+
const successful = [];
|
|
2900
|
+
const failed = [];
|
|
2901
|
+
const processBatch = async (batch) => {
|
|
2902
|
+
const results = await Promise.allSettled(
|
|
2903
|
+
batch.map((file) => {
|
|
2904
|
+
try {
|
|
2905
|
+
const result = injectMetaTagsInFile(file, options);
|
|
2906
|
+
return Promise.resolve({ file, result, success: true });
|
|
2907
|
+
} catch (err) {
|
|
2908
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2909
|
+
if (!continueOnError) {
|
|
2910
|
+
return Promise.reject(new Error(`Failed to inject meta tags in ${file}: ${message}`));
|
|
2911
|
+
}
|
|
2912
|
+
return Promise.resolve({ file, error: message, success: false });
|
|
2913
|
+
}
|
|
2914
|
+
})
|
|
2915
|
+
);
|
|
2916
|
+
for (const result of results) {
|
|
2917
|
+
if (result.status === "fulfilled") {
|
|
2918
|
+
if (result.value.success) {
|
|
2919
|
+
successful.push({ file: result.value.file, result: result.value.result });
|
|
2920
|
+
} else {
|
|
2921
|
+
failed.push({ file: result.value.file, error: result.value.error });
|
|
2922
|
+
}
|
|
2923
|
+
} else {
|
|
2924
|
+
const errorMessage = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
2925
|
+
failed.push({ file: "unknown", error: errorMessage });
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
};
|
|
2929
|
+
for (let i = 0; i < files.length; i += concurrency) {
|
|
2930
|
+
const batch = files.slice(i, i + concurrency);
|
|
2931
|
+
await processBatch(batch);
|
|
2932
|
+
}
|
|
2933
|
+
return {
|
|
2934
|
+
successful,
|
|
2935
|
+
failed,
|
|
2936
|
+
totalProcessed: successful.length,
|
|
2937
|
+
totalFailed: failed.length
|
|
2938
|
+
};
|
|
2939
|
+
}
|
|
2738
2940
|
|
|
2739
2941
|
// src/validator/pwa-validator.ts
|
|
2740
|
-
var
|
|
2942
|
+
var import_fs14 = require("fs");
|
|
2741
2943
|
var import_path11 = require("path");
|
|
2742
2944
|
var import_promises = require("fs/promises");
|
|
2743
2945
|
function validateManifest(projectPath, outputDir) {
|
|
2744
2946
|
const errors = [];
|
|
2745
2947
|
const manifestPath = (0, import_path11.join)(outputDir, "manifest.json");
|
|
2746
|
-
if (!(0,
|
|
2948
|
+
if (!(0, import_fs14.existsSync)(manifestPath)) {
|
|
2747
2949
|
return {
|
|
2748
2950
|
exists: false,
|
|
2749
2951
|
valid: false,
|
|
@@ -2759,7 +2961,7 @@ function validateManifest(projectPath, outputDir) {
|
|
|
2759
2961
|
};
|
|
2760
2962
|
}
|
|
2761
2963
|
try {
|
|
2762
|
-
const manifestContent = (0,
|
|
2964
|
+
const manifestContent = (0, import_fs14.readFileSync)(manifestPath, "utf-8");
|
|
2763
2965
|
const manifest = JSON.parse(manifestContent);
|
|
2764
2966
|
if (!manifest.name || manifest.name.trim().length === 0) {
|
|
2765
2967
|
errors.push({
|
|
@@ -2873,7 +3075,7 @@ function validateIcons(projectPath, outputDir, manifest) {
|
|
|
2873
3075
|
for (const icon of manifest.icons) {
|
|
2874
3076
|
const iconPath = icon.src.startsWith("/") ? icon.src.substring(1) : icon.src;
|
|
2875
3077
|
const fullIconPath = (0, import_path11.join)(outputDir, iconPath);
|
|
2876
|
-
if (!(0,
|
|
3078
|
+
if (!(0, import_fs14.existsSync)(fullIconPath)) {
|
|
2877
3079
|
errors.push({
|
|
2878
3080
|
code: "ICON_FILE_MISSING",
|
|
2879
3081
|
message: `Icon file not found: ${iconPath}`,
|
|
@@ -2917,7 +3119,7 @@ function validateIcons(projectPath, outputDir, manifest) {
|
|
|
2917
3119
|
function validateServiceWorker(projectPath, outputDir) {
|
|
2918
3120
|
const errors = [];
|
|
2919
3121
|
const swPath = (0, import_path11.join)(outputDir, "sw.js");
|
|
2920
|
-
if (!(0,
|
|
3122
|
+
if (!(0, import_fs14.existsSync)(swPath)) {
|
|
2921
3123
|
return {
|
|
2922
3124
|
exists: false,
|
|
2923
3125
|
valid: false,
|
|
@@ -2933,7 +3135,7 @@ function validateServiceWorker(projectPath, outputDir) {
|
|
|
2933
3135
|
};
|
|
2934
3136
|
}
|
|
2935
3137
|
try {
|
|
2936
|
-
const swContent = (0,
|
|
3138
|
+
const swContent = (0, import_fs14.readFileSync)(swPath, "utf-8");
|
|
2937
3139
|
if (!swContent.includes("workbox") && !swContent.includes("serviceWorker")) {
|
|
2938
3140
|
errors.push({
|
|
2939
3141
|
code: "SERVICE_WORKER_INVALID",
|
|
@@ -3196,6 +3398,7 @@ async function validatePWA(options) {
|
|
|
3196
3398
|
generateSplashScreensOnly,
|
|
3197
3399
|
injectMetaTags,
|
|
3198
3400
|
injectMetaTagsInFile,
|
|
3401
|
+
injectMetaTagsInFilesBatch,
|
|
3199
3402
|
optimizeImage,
|
|
3200
3403
|
optimizeProject,
|
|
3201
3404
|
optimizeProjectImages,
|
package/dist/index.d.cts
CHANGED
|
@@ -253,6 +253,19 @@ declare function writeManifest(manifest: Manifest, outputDir: string): string;
|
|
|
253
253
|
*/
|
|
254
254
|
declare function generateAndWriteManifest(options: ManifestGeneratorOptions, outputDir: string): string;
|
|
255
255
|
|
|
256
|
+
interface IconValidationResult {
|
|
257
|
+
valid: boolean;
|
|
258
|
+
errors: string[];
|
|
259
|
+
warnings: string[];
|
|
260
|
+
suggestions: string[];
|
|
261
|
+
metadata?: {
|
|
262
|
+
width: number;
|
|
263
|
+
height: number;
|
|
264
|
+
format: string;
|
|
265
|
+
size: number;
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
256
269
|
interface IconSize {
|
|
257
270
|
width: number;
|
|
258
271
|
height: number;
|
|
@@ -272,11 +285,14 @@ interface IconGeneratorOptions {
|
|
|
272
285
|
splashSizes?: SplashScreenSize[];
|
|
273
286
|
format?: 'png' | 'webp';
|
|
274
287
|
quality?: number;
|
|
288
|
+
validate?: boolean;
|
|
289
|
+
strictValidation?: boolean;
|
|
275
290
|
}
|
|
276
291
|
interface IconGenerationResult {
|
|
277
292
|
icons: ManifestIcon[];
|
|
278
293
|
splashScreens: ManifestSplashScreen[];
|
|
279
294
|
generatedFiles: string[];
|
|
295
|
+
validation?: IconValidationResult;
|
|
280
296
|
}
|
|
281
297
|
/**
|
|
282
298
|
* Generates all PWA icons from a source image
|
|
@@ -448,6 +464,35 @@ declare function injectMetaTags(htmlContent: string, options?: MetaInjectorOptio
|
|
|
448
464
|
* Injects meta-tags into an HTML file
|
|
449
465
|
*/
|
|
450
466
|
declare function injectMetaTagsInFile(filePath: string, options?: MetaInjectorOptions): InjectionResult;
|
|
467
|
+
/**
|
|
468
|
+
* Batch process options for parallel HTML injection
|
|
469
|
+
*/
|
|
470
|
+
interface BatchInjectOptions {
|
|
471
|
+
files: string[];
|
|
472
|
+
options: MetaInjectorOptions;
|
|
473
|
+
concurrency?: number;
|
|
474
|
+
continueOnError?: boolean;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Result of batch injection
|
|
478
|
+
*/
|
|
479
|
+
interface BatchInjectResult {
|
|
480
|
+
successful: Array<{
|
|
481
|
+
file: string;
|
|
482
|
+
result: InjectionResult;
|
|
483
|
+
}>;
|
|
484
|
+
failed: Array<{
|
|
485
|
+
file: string;
|
|
486
|
+
error: string;
|
|
487
|
+
}>;
|
|
488
|
+
totalProcessed: number;
|
|
489
|
+
totalFailed: number;
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Injects PWA meta-tags into multiple HTML files in parallel with concurrency limit
|
|
493
|
+
* This significantly improves performance when processing many files
|
|
494
|
+
*/
|
|
495
|
+
declare function injectMetaTagsInFilesBatch(batchOptions: BatchInjectOptions): Promise<BatchInjectResult>;
|
|
451
496
|
|
|
452
497
|
interface ValidationError {
|
|
453
498
|
code: string;
|
|
@@ -509,4 +554,4 @@ interface PWAValidatorOptions {
|
|
|
509
554
|
*/
|
|
510
555
|
declare function validatePWA(options: PWAValidatorOptions): Promise<ValidationResult>;
|
|
511
556
|
|
|
512
|
-
export { type AdaptiveCacheStrategy, type ApiType, type Architecture, type ArchitectureDetectionResult, type AssetDetectionResult, type AssetOptimizationSuggestion, type BuildTool, type CacheStrategy, type Framework, type FrameworkDetectionResult, type FrameworkVersion, type HTMLParserOptions, type HttpsCheckResult, type HttpsCheckerOptions, type IconGenerationResult, type IconGeneratorOptions, type IconSize, type ImageOptimizationOptions, type InjectionResult, type Manifest, type ManifestGeneratorOptions, type ManifestIcon, ManifestSchema, type ManifestSplashScreen, type MetaInjectorOptions, type OptimizationResult, type OptimizedImageResult, type OptimizedManifestConfig, type PWAValidatorOptions, type ParsedHTML, type ProjectConfiguration, STANDARD_ICON_SIZES, STANDARD_SPLASH_SIZES, type ScannerOptions, type ScannerResult, type ServiceWorkerGenerationResult, type ServiceWorkerGeneratorOptions, type SplashScreenSize, type ValidationError, type ValidationResult, type ValidationWarning, checkHttps, checkProjectHttps, detectApiType, detectArchitecture, detectAssets, detectFramework, detectProjectUrl, detectUnoptimizedImages, elementExists, findAllElements, findElement, generateAdaptiveCacheStrategies, generateAndWriteManifest, generateAndWriteServiceWorker, generateAppleTouchIcon, generateFavicon, generateIcons, generateIconsOnly, generateManifest, generateOptimalShortName, generateReport, generateResponsiveImageSizes, generateServiceWorker, generateSimpleServiceWorker, generateSplashScreensOnly, injectMetaTags, injectMetaTagsInFile, optimizeImage, optimizeProject, optimizeProjectImages, parseHTML, parseHTMLFile, scanProject, serializeHTML, suggestManifestColors, validatePWA, validateProjectPath, writeManifest };
|
|
557
|
+
export { type AdaptiveCacheStrategy, type ApiType, type Architecture, type ArchitectureDetectionResult, type AssetDetectionResult, type AssetOptimizationSuggestion, type BatchInjectOptions, type BatchInjectResult, type BuildTool, type CacheStrategy, type Framework, type FrameworkDetectionResult, type FrameworkVersion, type HTMLParserOptions, type HttpsCheckResult, type HttpsCheckerOptions, type IconGenerationResult, type IconGeneratorOptions, type IconSize, type ImageOptimizationOptions, type InjectionResult, type Manifest, type ManifestGeneratorOptions, type ManifestIcon, ManifestSchema, type ManifestSplashScreen, type MetaInjectorOptions, type OptimizationResult, type OptimizedImageResult, type OptimizedManifestConfig, type PWAValidatorOptions, type ParsedHTML, type ProjectConfiguration, STANDARD_ICON_SIZES, STANDARD_SPLASH_SIZES, type ScannerOptions, type ScannerResult, type ServiceWorkerGenerationResult, type ServiceWorkerGeneratorOptions, type SplashScreenSize, type ValidationError, type ValidationResult, type ValidationWarning, checkHttps, checkProjectHttps, detectApiType, detectArchitecture, detectAssets, detectFramework, detectProjectUrl, detectUnoptimizedImages, elementExists, findAllElements, findElement, generateAdaptiveCacheStrategies, generateAndWriteManifest, generateAndWriteServiceWorker, generateAppleTouchIcon, generateFavicon, generateIcons, generateIconsOnly, generateManifest, generateOptimalShortName, generateReport, generateResponsiveImageSizes, generateServiceWorker, generateSimpleServiceWorker, generateSplashScreensOnly, injectMetaTags, injectMetaTagsInFile, injectMetaTagsInFilesBatch, optimizeImage, optimizeProject, optimizeProjectImages, parseHTML, parseHTMLFile, scanProject, serializeHTML, suggestManifestColors, validatePWA, validateProjectPath, writeManifest };
|
package/dist/index.d.ts
CHANGED
|
@@ -253,6 +253,19 @@ declare function writeManifest(manifest: Manifest, outputDir: string): string;
|
|
|
253
253
|
*/
|
|
254
254
|
declare function generateAndWriteManifest(options: ManifestGeneratorOptions, outputDir: string): string;
|
|
255
255
|
|
|
256
|
+
interface IconValidationResult {
|
|
257
|
+
valid: boolean;
|
|
258
|
+
errors: string[];
|
|
259
|
+
warnings: string[];
|
|
260
|
+
suggestions: string[];
|
|
261
|
+
metadata?: {
|
|
262
|
+
width: number;
|
|
263
|
+
height: number;
|
|
264
|
+
format: string;
|
|
265
|
+
size: number;
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
256
269
|
interface IconSize {
|
|
257
270
|
width: number;
|
|
258
271
|
height: number;
|
|
@@ -272,11 +285,14 @@ interface IconGeneratorOptions {
|
|
|
272
285
|
splashSizes?: SplashScreenSize[];
|
|
273
286
|
format?: 'png' | 'webp';
|
|
274
287
|
quality?: number;
|
|
288
|
+
validate?: boolean;
|
|
289
|
+
strictValidation?: boolean;
|
|
275
290
|
}
|
|
276
291
|
interface IconGenerationResult {
|
|
277
292
|
icons: ManifestIcon[];
|
|
278
293
|
splashScreens: ManifestSplashScreen[];
|
|
279
294
|
generatedFiles: string[];
|
|
295
|
+
validation?: IconValidationResult;
|
|
280
296
|
}
|
|
281
297
|
/**
|
|
282
298
|
* Generates all PWA icons from a source image
|
|
@@ -448,6 +464,35 @@ declare function injectMetaTags(htmlContent: string, options?: MetaInjectorOptio
|
|
|
448
464
|
* Injects meta-tags into an HTML file
|
|
449
465
|
*/
|
|
450
466
|
declare function injectMetaTagsInFile(filePath: string, options?: MetaInjectorOptions): InjectionResult;
|
|
467
|
+
/**
|
|
468
|
+
* Batch process options for parallel HTML injection
|
|
469
|
+
*/
|
|
470
|
+
interface BatchInjectOptions {
|
|
471
|
+
files: string[];
|
|
472
|
+
options: MetaInjectorOptions;
|
|
473
|
+
concurrency?: number;
|
|
474
|
+
continueOnError?: boolean;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Result of batch injection
|
|
478
|
+
*/
|
|
479
|
+
interface BatchInjectResult {
|
|
480
|
+
successful: Array<{
|
|
481
|
+
file: string;
|
|
482
|
+
result: InjectionResult;
|
|
483
|
+
}>;
|
|
484
|
+
failed: Array<{
|
|
485
|
+
file: string;
|
|
486
|
+
error: string;
|
|
487
|
+
}>;
|
|
488
|
+
totalProcessed: number;
|
|
489
|
+
totalFailed: number;
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Injects PWA meta-tags into multiple HTML files in parallel with concurrency limit
|
|
493
|
+
* This significantly improves performance when processing many files
|
|
494
|
+
*/
|
|
495
|
+
declare function injectMetaTagsInFilesBatch(batchOptions: BatchInjectOptions): Promise<BatchInjectResult>;
|
|
451
496
|
|
|
452
497
|
interface ValidationError {
|
|
453
498
|
code: string;
|
|
@@ -509,4 +554,4 @@ interface PWAValidatorOptions {
|
|
|
509
554
|
*/
|
|
510
555
|
declare function validatePWA(options: PWAValidatorOptions): Promise<ValidationResult>;
|
|
511
556
|
|
|
512
|
-
export { type AdaptiveCacheStrategy, type ApiType, type Architecture, type ArchitectureDetectionResult, type AssetDetectionResult, type AssetOptimizationSuggestion, type BuildTool, type CacheStrategy, type Framework, type FrameworkDetectionResult, type FrameworkVersion, type HTMLParserOptions, type HttpsCheckResult, type HttpsCheckerOptions, type IconGenerationResult, type IconGeneratorOptions, type IconSize, type ImageOptimizationOptions, type InjectionResult, type Manifest, type ManifestGeneratorOptions, type ManifestIcon, ManifestSchema, type ManifestSplashScreen, type MetaInjectorOptions, type OptimizationResult, type OptimizedImageResult, type OptimizedManifestConfig, type PWAValidatorOptions, type ParsedHTML, type ProjectConfiguration, STANDARD_ICON_SIZES, STANDARD_SPLASH_SIZES, type ScannerOptions, type ScannerResult, type ServiceWorkerGenerationResult, type ServiceWorkerGeneratorOptions, type SplashScreenSize, type ValidationError, type ValidationResult, type ValidationWarning, checkHttps, checkProjectHttps, detectApiType, detectArchitecture, detectAssets, detectFramework, detectProjectUrl, detectUnoptimizedImages, elementExists, findAllElements, findElement, generateAdaptiveCacheStrategies, generateAndWriteManifest, generateAndWriteServiceWorker, generateAppleTouchIcon, generateFavicon, generateIcons, generateIconsOnly, generateManifest, generateOptimalShortName, generateReport, generateResponsiveImageSizes, generateServiceWorker, generateSimpleServiceWorker, generateSplashScreensOnly, injectMetaTags, injectMetaTagsInFile, optimizeImage, optimizeProject, optimizeProjectImages, parseHTML, parseHTMLFile, scanProject, serializeHTML, suggestManifestColors, validatePWA, validateProjectPath, writeManifest };
|
|
557
|
+
export { type AdaptiveCacheStrategy, type ApiType, type Architecture, type ArchitectureDetectionResult, type AssetDetectionResult, type AssetOptimizationSuggestion, type BatchInjectOptions, type BatchInjectResult, type BuildTool, type CacheStrategy, type Framework, type FrameworkDetectionResult, type FrameworkVersion, type HTMLParserOptions, type HttpsCheckResult, type HttpsCheckerOptions, type IconGenerationResult, type IconGeneratorOptions, type IconSize, type ImageOptimizationOptions, type InjectionResult, type Manifest, type ManifestGeneratorOptions, type ManifestIcon, ManifestSchema, type ManifestSplashScreen, type MetaInjectorOptions, type OptimizationResult, type OptimizedImageResult, type OptimizedManifestConfig, type PWAValidatorOptions, type ParsedHTML, type ProjectConfiguration, STANDARD_ICON_SIZES, STANDARD_SPLASH_SIZES, type ScannerOptions, type ScannerResult, type ServiceWorkerGenerationResult, type ServiceWorkerGeneratorOptions, type SplashScreenSize, type ValidationError, type ValidationResult, type ValidationWarning, checkHttps, checkProjectHttps, detectApiType, detectArchitecture, detectAssets, detectFramework, detectProjectUrl, detectUnoptimizedImages, elementExists, findAllElements, findElement, generateAdaptiveCacheStrategies, generateAndWriteManifest, generateAndWriteServiceWorker, generateAppleTouchIcon, generateFavicon, generateIcons, generateIconsOnly, generateManifest, generateOptimalShortName, generateReport, generateResponsiveImageSizes, generateServiceWorker, generateSimpleServiceWorker, generateSplashScreensOnly, injectMetaTags, injectMetaTagsInFile, injectMetaTagsInFilesBatch, optimizeImage, optimizeProject, optimizeProjectImages, parseHTML, parseHTMLFile, scanProject, serializeHTML, suggestManifestColors, validatePWA, validateProjectPath, writeManifest };
|
package/dist/index.js
CHANGED
|
@@ -1570,9 +1570,11 @@ async function scanProject(options) {
|
|
|
1570
1570
|
buildTool: null
|
|
1571
1571
|
}
|
|
1572
1572
|
};
|
|
1573
|
-
const assetsCandidate =
|
|
1573
|
+
const [assetsCandidate, architectureCandidate] = await Promise.all([
|
|
1574
|
+
includeAssets ? detectAssets(projectPath) : Promise.resolve(getEmptyAssets()),
|
|
1575
|
+
includeArchitecture ? detectArchitecture(projectPath) : Promise.resolve(getEmptyArchitecture())
|
|
1576
|
+
]);
|
|
1574
1577
|
const assets = isAssetDetectionResult(assetsCandidate) ? assetsCandidate : getEmptyAssets();
|
|
1575
|
-
const architectureCandidate = includeArchitecture ? await detectArchitecture(projectPath) : getEmptyArchitecture();
|
|
1576
1578
|
const architecture = isArchitectureDetectionResult(architectureCandidate) ? architectureCandidate : getEmptyArchitecture();
|
|
1577
1579
|
const result = {
|
|
1578
1580
|
framework,
|
|
@@ -1738,9 +1740,117 @@ function generateAndWriteManifest(options, outputDir) {
|
|
|
1738
1740
|
}
|
|
1739
1741
|
|
|
1740
1742
|
// src/generator/icon-generator.ts
|
|
1741
|
-
import
|
|
1742
|
-
import { existsSync as
|
|
1743
|
+
import sharp3 from "sharp";
|
|
1744
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
|
|
1743
1745
|
import { join as join8 } from "path";
|
|
1746
|
+
|
|
1747
|
+
// src/validator/icon-validator.ts
|
|
1748
|
+
import sharp2 from "sharp";
|
|
1749
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1750
|
+
async function validateIconSource(options) {
|
|
1751
|
+
const {
|
|
1752
|
+
sourceImage,
|
|
1753
|
+
strict = false,
|
|
1754
|
+
minRecommendedSize = 192,
|
|
1755
|
+
optimalSize = 512
|
|
1756
|
+
} = options;
|
|
1757
|
+
const result = {
|
|
1758
|
+
valid: true,
|
|
1759
|
+
errors: [],
|
|
1760
|
+
warnings: [],
|
|
1761
|
+
suggestions: []
|
|
1762
|
+
};
|
|
1763
|
+
if (!existsSync6(sourceImage)) {
|
|
1764
|
+
result.valid = false;
|
|
1765
|
+
result.errors.push(`Source image not found: ${sourceImage}`);
|
|
1766
|
+
return result;
|
|
1767
|
+
}
|
|
1768
|
+
try {
|
|
1769
|
+
const image = sharp2(sourceImage);
|
|
1770
|
+
const metadata = await image.metadata();
|
|
1771
|
+
if (!metadata.width || !metadata.height) {
|
|
1772
|
+
result.valid = false;
|
|
1773
|
+
result.errors.push("Unable to read image dimensions");
|
|
1774
|
+
return result;
|
|
1775
|
+
}
|
|
1776
|
+
const { width, height, format, size } = metadata;
|
|
1777
|
+
const minDimension = Math.min(width, height);
|
|
1778
|
+
const maxDimension = Math.max(width, height);
|
|
1779
|
+
const isSquare = width === height;
|
|
1780
|
+
result.metadata = {
|
|
1781
|
+
width,
|
|
1782
|
+
height,
|
|
1783
|
+
format: format || "unknown",
|
|
1784
|
+
size: size || 0
|
|
1785
|
+
};
|
|
1786
|
+
const supportedFormats = ["png", "jpeg", "jpg", "webp", "svg"];
|
|
1787
|
+
if (!format || !supportedFormats.includes(format.toLowerCase())) {
|
|
1788
|
+
result.valid = false;
|
|
1789
|
+
result.errors.push(
|
|
1790
|
+
`Unsupported image format: ${format || "unknown"}. Supported formats: ${supportedFormats.join(", ")}`
|
|
1791
|
+
);
|
|
1792
|
+
}
|
|
1793
|
+
if (minDimension < minRecommendedSize) {
|
|
1794
|
+
const message = `Icon dimensions too small: ${width}x${height}. Minimum recommended: ${minRecommendedSize}x${minRecommendedSize}`;
|
|
1795
|
+
if (strict) {
|
|
1796
|
+
result.valid = false;
|
|
1797
|
+
result.errors.push(message);
|
|
1798
|
+
} else {
|
|
1799
|
+
result.warnings.push(message);
|
|
1800
|
+
result.suggestions.push(
|
|
1801
|
+
`Use an image at least ${minRecommendedSize}x${minRecommendedSize} pixels for best PWA compatibility`
|
|
1802
|
+
);
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
if (minDimension < optimalSize) {
|
|
1806
|
+
result.warnings.push(
|
|
1807
|
+
`Icon dimensions below optimal size: ${width}x${height}. Optimal size: ${optimalSize}x${optimalSize} for best quality on all devices`
|
|
1808
|
+
);
|
|
1809
|
+
result.suggestions.push(
|
|
1810
|
+
`Consider using a ${optimalSize}x${optimalSize} source image for optimal icon quality`
|
|
1811
|
+
);
|
|
1812
|
+
}
|
|
1813
|
+
if (!isSquare) {
|
|
1814
|
+
result.warnings.push(
|
|
1815
|
+
`Icon is not square (${width}x${height}). Square images work best for PWA icons`
|
|
1816
|
+
);
|
|
1817
|
+
result.suggestions.push(
|
|
1818
|
+
"Consider using a square source image. The icon will be cropped to fit during generation"
|
|
1819
|
+
);
|
|
1820
|
+
}
|
|
1821
|
+
if (format?.toLowerCase() === "jpg" || format?.toLowerCase() === "jpeg") {
|
|
1822
|
+
result.suggestions.push(
|
|
1823
|
+
"PNG format is recommended for icons with transparency. Consider converting JPG to PNG"
|
|
1824
|
+
);
|
|
1825
|
+
}
|
|
1826
|
+
if (size && size > 1024 * 1024) {
|
|
1827
|
+
const sizeMB = (size / (1024 * 1024)).toFixed(2);
|
|
1828
|
+
result.warnings.push(`Icon file size is large: ${sizeMB}MB. This may slow down generation`);
|
|
1829
|
+
result.suggestions.push(
|
|
1830
|
+
"Consider optimizing the source image before generation to reduce file size"
|
|
1831
|
+
);
|
|
1832
|
+
} else if (size && size > 500 * 1024) {
|
|
1833
|
+
const sizeKB = (size / 1024).toFixed(2);
|
|
1834
|
+
result.suggestions.push(
|
|
1835
|
+
`Icon file size is ${sizeKB}KB. Consider optimizing to reduce generation time`
|
|
1836
|
+
);
|
|
1837
|
+
}
|
|
1838
|
+
const aspectRatio = maxDimension / minDimension;
|
|
1839
|
+
if (aspectRatio > 2) {
|
|
1840
|
+
result.warnings.push(
|
|
1841
|
+
`Icon has extreme aspect ratio (${aspectRatio.toFixed(2)}:1). Square images are recommended`
|
|
1842
|
+
);
|
|
1843
|
+
}
|
|
1844
|
+
return result;
|
|
1845
|
+
} catch (error) {
|
|
1846
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1847
|
+
result.valid = false;
|
|
1848
|
+
result.errors.push(`Failed to validate icon: ${message}`);
|
|
1849
|
+
return result;
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
// src/generator/icon-generator.ts
|
|
1744
1854
|
var STANDARD_ICON_SIZES = [
|
|
1745
1855
|
{ width: 72, height: 72, name: "icon-72x72.png" },
|
|
1746
1856
|
{ width: 96, height: 96, name: "icon-96x96.png" },
|
|
@@ -1774,67 +1884,114 @@ async function generateIcons(options) {
|
|
|
1774
1884
|
iconSizes = STANDARD_ICON_SIZES,
|
|
1775
1885
|
splashSizes = STANDARD_SPLASH_SIZES,
|
|
1776
1886
|
format = "png",
|
|
1777
|
-
quality = 90
|
|
1887
|
+
quality = 90,
|
|
1888
|
+
validate = false,
|
|
1889
|
+
strictValidation = false
|
|
1778
1890
|
} = options;
|
|
1779
|
-
if (!
|
|
1891
|
+
if (!existsSync7(sourceImage)) {
|
|
1780
1892
|
throw new Error(`Source image not found: ${sourceImage}`);
|
|
1781
1893
|
}
|
|
1894
|
+
let validation;
|
|
1895
|
+
if (validate) {
|
|
1896
|
+
validation = await validateIconSource({
|
|
1897
|
+
sourceImage,
|
|
1898
|
+
strict: strictValidation
|
|
1899
|
+
});
|
|
1900
|
+
if (strictValidation && !validation.valid) {
|
|
1901
|
+
const errorMessages = validation.errors.join("; ");
|
|
1902
|
+
throw new Error(`Icon validation failed: ${errorMessages}`);
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1782
1905
|
mkdirSync2(outputDir, { recursive: true });
|
|
1783
1906
|
const generatedFiles = [];
|
|
1784
1907
|
const icons = [];
|
|
1785
1908
|
const splashScreens = [];
|
|
1786
|
-
const image =
|
|
1909
|
+
const image = sharp3(sourceImage);
|
|
1787
1910
|
const metadata = await image.metadata();
|
|
1788
1911
|
if (!metadata.width || !metadata.height) {
|
|
1789
1912
|
throw new Error("Unable to read image dimensions");
|
|
1790
1913
|
}
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1914
|
+
const iconResults = await Promise.all(
|
|
1915
|
+
iconSizes.map(async (size) => {
|
|
1916
|
+
const outputPath = join8(outputDir, size.name);
|
|
1917
|
+
try {
|
|
1918
|
+
let pipeline = image.clone().resize(size.width, size.height, {
|
|
1919
|
+
fit: "cover",
|
|
1920
|
+
position: "center"
|
|
1921
|
+
});
|
|
1922
|
+
if (format === "png") {
|
|
1923
|
+
pipeline = pipeline.png({ quality, compressionLevel: 9 });
|
|
1924
|
+
} else {
|
|
1925
|
+
pipeline = pipeline.webp({ quality });
|
|
1926
|
+
}
|
|
1927
|
+
await pipeline.toFile(outputPath);
|
|
1928
|
+
return {
|
|
1929
|
+
success: true,
|
|
1930
|
+
file: outputPath,
|
|
1931
|
+
icon: {
|
|
1932
|
+
src: `/${size.name}`,
|
|
1933
|
+
sizes: `${size.width}x${size.height}`,
|
|
1934
|
+
type: format === "png" ? "image/png" : "image/webp",
|
|
1935
|
+
purpose: size.width >= 192 && size.width <= 512 ? "any" : void 0
|
|
1936
|
+
}
|
|
1937
|
+
};
|
|
1938
|
+
} catch (err) {
|
|
1939
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1940
|
+
return {
|
|
1941
|
+
success: false,
|
|
1942
|
+
error: `Failed to generate icon ${size.name}: ${message}`,
|
|
1943
|
+
size: size.name
|
|
1944
|
+
};
|
|
1802
1945
|
}
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
} catch (err) {
|
|
1812
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1813
|
-
throw new Error(`Failed to generate icon ${size.name}: ${message}`);
|
|
1946
|
+
})
|
|
1947
|
+
);
|
|
1948
|
+
for (const result of iconResults) {
|
|
1949
|
+
if (result.success) {
|
|
1950
|
+
generatedFiles.push(result.file);
|
|
1951
|
+
icons.push(result.icon);
|
|
1952
|
+
} else {
|
|
1953
|
+
throw new Error(result.error);
|
|
1814
1954
|
}
|
|
1815
1955
|
}
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1956
|
+
const splashResults = await Promise.all(
|
|
1957
|
+
splashSizes.map(async (size) => {
|
|
1958
|
+
const outputPath = join8(outputDir, size.name);
|
|
1959
|
+
try {
|
|
1960
|
+
let pipeline = image.clone().resize(size.width, size.height, {
|
|
1961
|
+
fit: "cover",
|
|
1962
|
+
position: "center"
|
|
1963
|
+
});
|
|
1964
|
+
if (format === "png") {
|
|
1965
|
+
pipeline = pipeline.png({ quality, compressionLevel: 9 });
|
|
1966
|
+
} else {
|
|
1967
|
+
pipeline = pipeline.webp({ quality });
|
|
1968
|
+
}
|
|
1969
|
+
await pipeline.toFile(outputPath);
|
|
1970
|
+
return {
|
|
1971
|
+
success: true,
|
|
1972
|
+
file: outputPath,
|
|
1973
|
+
splash: {
|
|
1974
|
+
src: `/${size.name}`,
|
|
1975
|
+
sizes: `${size.width}x${size.height}`,
|
|
1976
|
+
type: format === "png" ? "image/png" : "image/webp"
|
|
1977
|
+
}
|
|
1978
|
+
};
|
|
1979
|
+
} catch (err) {
|
|
1980
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1981
|
+
return {
|
|
1982
|
+
success: false,
|
|
1983
|
+
error: `Failed to generate splash screen ${size.name}: ${message}`,
|
|
1984
|
+
size: size.name
|
|
1985
|
+
};
|
|
1827
1986
|
}
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1837
|
-
throw new Error(`Failed to generate splash screen ${size.name}: ${message}`);
|
|
1987
|
+
})
|
|
1988
|
+
);
|
|
1989
|
+
for (const result of splashResults) {
|
|
1990
|
+
if (result.success) {
|
|
1991
|
+
generatedFiles.push(result.file);
|
|
1992
|
+
splashScreens.push(result.splash);
|
|
1993
|
+
} else {
|
|
1994
|
+
throw new Error(result.error);
|
|
1838
1995
|
}
|
|
1839
1996
|
}
|
|
1840
1997
|
if (format === "png") {
|
|
@@ -1853,7 +2010,8 @@ async function generateIcons(options) {
|
|
|
1853
2010
|
return {
|
|
1854
2011
|
icons,
|
|
1855
2012
|
splashScreens,
|
|
1856
|
-
generatedFiles
|
|
2013
|
+
generatedFiles,
|
|
2014
|
+
validation
|
|
1857
2015
|
};
|
|
1858
2016
|
}
|
|
1859
2017
|
async function generateIconsOnly(options) {
|
|
@@ -1877,13 +2035,13 @@ async function generateSplashScreensOnly(options) {
|
|
|
1877
2035
|
};
|
|
1878
2036
|
}
|
|
1879
2037
|
async function generateFavicon(sourceImage, outputDir) {
|
|
1880
|
-
if (!
|
|
2038
|
+
if (!existsSync7(sourceImage)) {
|
|
1881
2039
|
throw new Error(`Source image not found: ${sourceImage}`);
|
|
1882
2040
|
}
|
|
1883
2041
|
mkdirSync2(outputDir, { recursive: true });
|
|
1884
2042
|
const faviconPath = join8(outputDir, "favicon.ico");
|
|
1885
2043
|
try {
|
|
1886
|
-
await
|
|
2044
|
+
await sharp3(sourceImage).resize(32, 32, {
|
|
1887
2045
|
fit: "cover",
|
|
1888
2046
|
position: "center"
|
|
1889
2047
|
}).png().toFile(faviconPath);
|
|
@@ -1894,13 +2052,13 @@ async function generateFavicon(sourceImage, outputDir) {
|
|
|
1894
2052
|
}
|
|
1895
2053
|
}
|
|
1896
2054
|
async function generateAppleTouchIcon(sourceImage, outputDir) {
|
|
1897
|
-
if (!
|
|
2055
|
+
if (!existsSync7(sourceImage)) {
|
|
1898
2056
|
throw new Error(`Source image not found: ${sourceImage}`);
|
|
1899
2057
|
}
|
|
1900
2058
|
mkdirSync2(outputDir, { recursive: true });
|
|
1901
2059
|
const appleIconPath = join8(outputDir, "apple-touch-icon.png");
|
|
1902
2060
|
try {
|
|
1903
|
-
await
|
|
2061
|
+
await sharp3(sourceImage).resize(180, 180, {
|
|
1904
2062
|
fit: "cover",
|
|
1905
2063
|
position: "center"
|
|
1906
2064
|
}).png({ quality: 90, compressionLevel: 9 }).toFile(appleIconPath);
|
|
@@ -1913,7 +2071,7 @@ async function generateAppleTouchIcon(sourceImage, outputDir) {
|
|
|
1913
2071
|
|
|
1914
2072
|
// src/generator/service-worker-generator.ts
|
|
1915
2073
|
import { injectManifest, generateSW } from "workbox-build";
|
|
1916
|
-
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as
|
|
2074
|
+
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync8 } from "fs";
|
|
1917
2075
|
import { join as join9 } from "path";
|
|
1918
2076
|
import { getServiceWorkerTemplate, determineTemplateType } from "@julien-lin/universal-pwa-templates";
|
|
1919
2077
|
async function generateServiceWorker(options) {
|
|
@@ -1948,7 +2106,7 @@ async function generateServiceWorker(options) {
|
|
|
1948
2106
|
try {
|
|
1949
2107
|
const result = await injectManifest(workboxConfig);
|
|
1950
2108
|
try {
|
|
1951
|
-
if (
|
|
2109
|
+
if (existsSync8(swSrcPath)) {
|
|
1952
2110
|
}
|
|
1953
2111
|
} catch {
|
|
1954
2112
|
}
|
|
@@ -2033,7 +2191,7 @@ async function generateAndWriteServiceWorker(options) {
|
|
|
2033
2191
|
}
|
|
2034
2192
|
|
|
2035
2193
|
// src/generator/https-checker.ts
|
|
2036
|
-
import { existsSync as
|
|
2194
|
+
import { existsSync as existsSync9, readFileSync as readFileSync5 } from "fs";
|
|
2037
2195
|
import { join as join10 } from "path";
|
|
2038
2196
|
function checkHttps(url, allowHttpLocalhost = true) {
|
|
2039
2197
|
let parsedUrl;
|
|
@@ -2090,7 +2248,7 @@ function detectProjectUrl(projectPath) {
|
|
|
2090
2248
|
];
|
|
2091
2249
|
for (const config of configFiles) {
|
|
2092
2250
|
const filePath = join10(projectPath, config.file);
|
|
2093
|
-
if (
|
|
2251
|
+
if (existsSync9(filePath)) {
|
|
2094
2252
|
try {
|
|
2095
2253
|
if (config.file.endsWith(".json") && config.key) {
|
|
2096
2254
|
const parsed = JSON.parse(readFileSync5(filePath, "utf-8"));
|
|
@@ -2659,15 +2817,58 @@ function injectMetaTagsInFile(filePath, options = {}) {
|
|
|
2659
2817
|
writeFileSync4(filePath, html, "utf-8");
|
|
2660
2818
|
return result;
|
|
2661
2819
|
}
|
|
2820
|
+
async function injectMetaTagsInFilesBatch(batchOptions) {
|
|
2821
|
+
const { files, options, concurrency = 5, continueOnError = true } = batchOptions;
|
|
2822
|
+
const successful = [];
|
|
2823
|
+
const failed = [];
|
|
2824
|
+
const processBatch = async (batch) => {
|
|
2825
|
+
const results = await Promise.allSettled(
|
|
2826
|
+
batch.map((file) => {
|
|
2827
|
+
try {
|
|
2828
|
+
const result = injectMetaTagsInFile(file, options);
|
|
2829
|
+
return Promise.resolve({ file, result, success: true });
|
|
2830
|
+
} catch (err) {
|
|
2831
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2832
|
+
if (!continueOnError) {
|
|
2833
|
+
return Promise.reject(new Error(`Failed to inject meta tags in ${file}: ${message}`));
|
|
2834
|
+
}
|
|
2835
|
+
return Promise.resolve({ file, error: message, success: false });
|
|
2836
|
+
}
|
|
2837
|
+
})
|
|
2838
|
+
);
|
|
2839
|
+
for (const result of results) {
|
|
2840
|
+
if (result.status === "fulfilled") {
|
|
2841
|
+
if (result.value.success) {
|
|
2842
|
+
successful.push({ file: result.value.file, result: result.value.result });
|
|
2843
|
+
} else {
|
|
2844
|
+
failed.push({ file: result.value.file, error: result.value.error });
|
|
2845
|
+
}
|
|
2846
|
+
} else {
|
|
2847
|
+
const errorMessage = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
2848
|
+
failed.push({ file: "unknown", error: errorMessage });
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
};
|
|
2852
|
+
for (let i = 0; i < files.length; i += concurrency) {
|
|
2853
|
+
const batch = files.slice(i, i + concurrency);
|
|
2854
|
+
await processBatch(batch);
|
|
2855
|
+
}
|
|
2856
|
+
return {
|
|
2857
|
+
successful,
|
|
2858
|
+
failed,
|
|
2859
|
+
totalProcessed: successful.length,
|
|
2860
|
+
totalFailed: failed.length
|
|
2861
|
+
};
|
|
2862
|
+
}
|
|
2662
2863
|
|
|
2663
2864
|
// src/validator/pwa-validator.ts
|
|
2664
|
-
import { existsSync as
|
|
2865
|
+
import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
|
|
2665
2866
|
import { join as join11 } from "path";
|
|
2666
2867
|
import { readFile } from "fs/promises";
|
|
2667
2868
|
function validateManifest(projectPath, outputDir) {
|
|
2668
2869
|
const errors = [];
|
|
2669
2870
|
const manifestPath = join11(outputDir, "manifest.json");
|
|
2670
|
-
if (!
|
|
2871
|
+
if (!existsSync10(manifestPath)) {
|
|
2671
2872
|
return {
|
|
2672
2873
|
exists: false,
|
|
2673
2874
|
valid: false,
|
|
@@ -2797,7 +2998,7 @@ function validateIcons(projectPath, outputDir, manifest) {
|
|
|
2797
2998
|
for (const icon of manifest.icons) {
|
|
2798
2999
|
const iconPath = icon.src.startsWith("/") ? icon.src.substring(1) : icon.src;
|
|
2799
3000
|
const fullIconPath = join11(outputDir, iconPath);
|
|
2800
|
-
if (!
|
|
3001
|
+
if (!existsSync10(fullIconPath)) {
|
|
2801
3002
|
errors.push({
|
|
2802
3003
|
code: "ICON_FILE_MISSING",
|
|
2803
3004
|
message: `Icon file not found: ${iconPath}`,
|
|
@@ -2841,7 +3042,7 @@ function validateIcons(projectPath, outputDir, manifest) {
|
|
|
2841
3042
|
function validateServiceWorker(projectPath, outputDir) {
|
|
2842
3043
|
const errors = [];
|
|
2843
3044
|
const swPath = join11(outputDir, "sw.js");
|
|
2844
|
-
if (!
|
|
3045
|
+
if (!existsSync10(swPath)) {
|
|
2845
3046
|
return {
|
|
2846
3047
|
exists: false,
|
|
2847
3048
|
valid: false,
|
|
@@ -3119,6 +3320,7 @@ export {
|
|
|
3119
3320
|
generateSplashScreensOnly,
|
|
3120
3321
|
injectMetaTags,
|
|
3121
3322
|
injectMetaTagsInFile,
|
|
3323
|
+
injectMetaTagsInFilesBatch,
|
|
3122
3324
|
optimizeImage,
|
|
3123
3325
|
optimizeProject,
|
|
3124
3326
|
optimizeProjectImages,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@julien-lin/universal-pwa-core",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.6",
|
|
4
4
|
"description": "Core engine for scanning, generation, and injection for UniversalPWA",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pwa",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"workbox-routing": "^7.4.0",
|
|
62
62
|
"workbox-strategies": "^7.4.0",
|
|
63
63
|
"zod": "^4.2.1",
|
|
64
|
-
"@julien-lin/universal-pwa-templates": "^1.3.
|
|
64
|
+
"@julien-lin/universal-pwa-templates": "^1.3.6"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@vitest/coverage-v8": "^2.1.4",
|