@universal-pwa/core 0.1.0
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/LICENSE +22 -0
- package/README.md +151 -0
- package/dist/index.cjs +1368 -0
- package/dist/index.d.cts +348 -0
- package/dist/index.d.ts +348 -0
- package/dist/index.js +1301 -0
- package/package.json +67 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1368 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
ManifestSchema: () => ManifestSchema,
|
|
34
|
+
STANDARD_ICON_SIZES: () => STANDARD_ICON_SIZES,
|
|
35
|
+
STANDARD_SPLASH_SIZES: () => STANDARD_SPLASH_SIZES,
|
|
36
|
+
checkHttps: () => checkHttps,
|
|
37
|
+
checkProjectHttps: () => checkProjectHttps,
|
|
38
|
+
detectArchitecture: () => detectArchitecture,
|
|
39
|
+
detectAssets: () => detectAssets,
|
|
40
|
+
detectFramework: () => detectFramework,
|
|
41
|
+
detectProjectUrl: () => detectProjectUrl,
|
|
42
|
+
elementExists: () => elementExists,
|
|
43
|
+
findAllElements: () => findAllElements,
|
|
44
|
+
findElement: () => findElement,
|
|
45
|
+
generateAndWriteManifest: () => generateAndWriteManifest,
|
|
46
|
+
generateAndWriteServiceWorker: () => generateAndWriteServiceWorker,
|
|
47
|
+
generateAppleTouchIcon: () => generateAppleTouchIcon,
|
|
48
|
+
generateFavicon: () => generateFavicon,
|
|
49
|
+
generateIcons: () => generateIcons,
|
|
50
|
+
generateIconsOnly: () => generateIconsOnly,
|
|
51
|
+
generateManifest: () => generateManifest,
|
|
52
|
+
generateReport: () => generateReport,
|
|
53
|
+
generateServiceWorker: () => generateServiceWorker,
|
|
54
|
+
generateSimpleServiceWorker: () => generateSimpleServiceWorker,
|
|
55
|
+
generateSplashScreensOnly: () => generateSplashScreensOnly,
|
|
56
|
+
injectMetaTags: () => injectMetaTags,
|
|
57
|
+
injectMetaTagsInFile: () => injectMetaTagsInFile,
|
|
58
|
+
parseHTML: () => parseHTML,
|
|
59
|
+
parseHTMLFile: () => parseHTMLFile,
|
|
60
|
+
scanProject: () => scanProject,
|
|
61
|
+
serializeHTML: () => serializeHTML,
|
|
62
|
+
validateProjectPath: () => validateProjectPath,
|
|
63
|
+
writeManifest: () => writeManifest
|
|
64
|
+
});
|
|
65
|
+
module.exports = __toCommonJS(index_exports);
|
|
66
|
+
|
|
67
|
+
// src/scanner/index.ts
|
|
68
|
+
var import_fs4 = require("fs");
|
|
69
|
+
|
|
70
|
+
// src/scanner/framework-detector.ts
|
|
71
|
+
var import_fs = require("fs");
|
|
72
|
+
var import_path = require("path");
|
|
73
|
+
function detectFramework(projectPath) {
|
|
74
|
+
const indicators = [];
|
|
75
|
+
let framework = null;
|
|
76
|
+
let confidence = "low";
|
|
77
|
+
if ((0, import_fs.existsSync)((0, import_path.join)(projectPath, "wp-config.php"))) {
|
|
78
|
+
indicators.push("wp-config.php");
|
|
79
|
+
if ((0, import_fs.existsSync)((0, import_path.join)(projectPath, "wp-content"))) {
|
|
80
|
+
indicators.push("wp-content/");
|
|
81
|
+
framework = "wordpress";
|
|
82
|
+
confidence = "high";
|
|
83
|
+
return { framework, confidence, indicators };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const composerPath = (0, import_path.join)(projectPath, "composer.json");
|
|
87
|
+
if ((0, import_fs.existsSync)(composerPath)) {
|
|
88
|
+
try {
|
|
89
|
+
const composerContent = JSON.parse((0, import_fs.readFileSync)(composerPath, "utf-8"));
|
|
90
|
+
const dependencies = {
|
|
91
|
+
...composerContent.require ?? {},
|
|
92
|
+
...composerContent["require-dev"] ?? {}
|
|
93
|
+
};
|
|
94
|
+
if (dependencies["symfony/symfony"] || dependencies["symfony/framework-bundle"]) {
|
|
95
|
+
indicators.push("composer.json: symfony/*");
|
|
96
|
+
if ((0, import_fs.existsSync)((0, import_path.join)(projectPath, "public"))) {
|
|
97
|
+
indicators.push("public/");
|
|
98
|
+
framework = "symfony";
|
|
99
|
+
confidence = "high";
|
|
100
|
+
return { framework, confidence, indicators };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (dependencies["laravel/framework"]) {
|
|
104
|
+
indicators.push("composer.json: laravel/framework");
|
|
105
|
+
if ((0, import_fs.existsSync)((0, import_path.join)(projectPath, "public"))) {
|
|
106
|
+
indicators.push("public/");
|
|
107
|
+
framework = "laravel";
|
|
108
|
+
confidence = "high";
|
|
109
|
+
return { framework, confidence, indicators };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const packageJsonPath = (0, import_path.join)(projectPath, "package.json");
|
|
116
|
+
if ((0, import_fs.existsSync)(packageJsonPath)) {
|
|
117
|
+
try {
|
|
118
|
+
const packageContent = JSON.parse((0, import_fs.readFileSync)(packageJsonPath, "utf-8"));
|
|
119
|
+
const dependencies = {
|
|
120
|
+
...packageContent.dependencies ?? {},
|
|
121
|
+
...packageContent.devDependencies ?? {}
|
|
122
|
+
};
|
|
123
|
+
if (dependencies.next) {
|
|
124
|
+
indicators.push("package.json: next");
|
|
125
|
+
if ((0, import_fs.existsSync)((0, import_path.join)(projectPath, ".next"))) {
|
|
126
|
+
indicators.push(".next/");
|
|
127
|
+
framework = "nextjs";
|
|
128
|
+
confidence = "high";
|
|
129
|
+
return { framework, confidence, indicators };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (dependencies.nuxt) {
|
|
133
|
+
indicators.push("package.json: nuxt");
|
|
134
|
+
if ((0, import_fs.existsSync)((0, import_path.join)(projectPath, ".nuxt"))) {
|
|
135
|
+
indicators.push(".nuxt/");
|
|
136
|
+
framework = "nuxt";
|
|
137
|
+
confidence = "high";
|
|
138
|
+
return { framework, confidence, indicators };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (dependencies.react) {
|
|
142
|
+
indicators.push("package.json: react");
|
|
143
|
+
framework = "react";
|
|
144
|
+
confidence = framework ? "high" : "medium";
|
|
145
|
+
}
|
|
146
|
+
if (dependencies.vue) {
|
|
147
|
+
indicators.push("package.json: vue");
|
|
148
|
+
if (!framework) {
|
|
149
|
+
framework = "vue";
|
|
150
|
+
confidence = "high";
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (dependencies["@angular/core"]) {
|
|
154
|
+
indicators.push("package.json: @angular/core");
|
|
155
|
+
if (!framework) {
|
|
156
|
+
framework = "angular";
|
|
157
|
+
confidence = "high";
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
} catch {
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (!framework) {
|
|
164
|
+
const htmlFiles = ["index.html", "index.htm"];
|
|
165
|
+
const hasHtml = htmlFiles.some((file) => (0, import_fs.existsSync)((0, import_path.join)(projectPath, file)));
|
|
166
|
+
if (hasHtml) {
|
|
167
|
+
indicators.push("HTML files present");
|
|
168
|
+
framework = "static";
|
|
169
|
+
confidence = "medium";
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return { framework, confidence, indicators };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/scanner/asset-detector.ts
|
|
176
|
+
var import_glob = require("glob");
|
|
177
|
+
var import_path2 = require("path");
|
|
178
|
+
var import_fs2 = require("fs");
|
|
179
|
+
var IGNORED_PATTERNS = [
|
|
180
|
+
"**/node_modules/**",
|
|
181
|
+
"**/.git/**",
|
|
182
|
+
"**/dist/**",
|
|
183
|
+
"**/.next/**",
|
|
184
|
+
"**/.nuxt/**",
|
|
185
|
+
"**/build/**",
|
|
186
|
+
"**/coverage/**"
|
|
187
|
+
];
|
|
188
|
+
var JS_EXTENSIONS = [".js", ".mjs", ".ts", ".tsx", ".jsx"];
|
|
189
|
+
var CSS_EXTENSIONS = [".css", ".scss", ".sass", ".less"];
|
|
190
|
+
var IMAGE_EXTENSIONS = [".png", ".jpg", ".jpeg", ".svg", ".webp", ".gif", ".ico"];
|
|
191
|
+
var FONT_EXTENSIONS = [".woff", ".woff2", ".ttf", ".otf", ".eot"];
|
|
192
|
+
var API_PATTERNS = [
|
|
193
|
+
"/api/**",
|
|
194
|
+
"/graphql",
|
|
195
|
+
"/rest/**",
|
|
196
|
+
"/v1/**",
|
|
197
|
+
"/v2/**"
|
|
198
|
+
];
|
|
199
|
+
async function detectAssets(projectPath) {
|
|
200
|
+
const result = {
|
|
201
|
+
javascript: [],
|
|
202
|
+
css: [],
|
|
203
|
+
images: [],
|
|
204
|
+
fonts: [],
|
|
205
|
+
apiRoutes: []
|
|
206
|
+
};
|
|
207
|
+
const jsExtensionsStr = JS_EXTENSIONS.map((ext) => ext.slice(1)).join(",");
|
|
208
|
+
const jsPattern = `**/*.{${jsExtensionsStr}}`;
|
|
209
|
+
const jsRootPattern = `*.{${jsExtensionsStr}}`;
|
|
210
|
+
const [jsFiles, jsRootFiles] = await Promise.all([
|
|
211
|
+
(0, import_glob.glob)(jsPattern, {
|
|
212
|
+
cwd: projectPath,
|
|
213
|
+
ignore: IGNORED_PATTERNS,
|
|
214
|
+
absolute: true,
|
|
215
|
+
nodir: true
|
|
216
|
+
}),
|
|
217
|
+
(0, import_glob.glob)(jsRootPattern, {
|
|
218
|
+
cwd: projectPath,
|
|
219
|
+
ignore: IGNORED_PATTERNS,
|
|
220
|
+
absolute: true,
|
|
221
|
+
nodir: true
|
|
222
|
+
})
|
|
223
|
+
]);
|
|
224
|
+
const allJsFiles = /* @__PURE__ */ new Set([...jsFiles, ...jsRootFiles]);
|
|
225
|
+
result.javascript.push(...allJsFiles);
|
|
226
|
+
const cssExtensionsStr = CSS_EXTENSIONS.map((ext) => ext.slice(1)).join(",");
|
|
227
|
+
const cssPattern = `**/*.{${cssExtensionsStr}}`;
|
|
228
|
+
const cssFiles = await (0, import_glob.glob)(cssPattern, {
|
|
229
|
+
cwd: projectPath,
|
|
230
|
+
ignore: IGNORED_PATTERNS,
|
|
231
|
+
absolute: false,
|
|
232
|
+
nodir: true
|
|
233
|
+
});
|
|
234
|
+
const cssRootPattern = `*.{${cssExtensionsStr}}`;
|
|
235
|
+
const cssRootFiles = await (0, import_glob.glob)(cssRootPattern, {
|
|
236
|
+
cwd: projectPath,
|
|
237
|
+
ignore: IGNORED_PATTERNS,
|
|
238
|
+
absolute: false,
|
|
239
|
+
nodir: true
|
|
240
|
+
});
|
|
241
|
+
const allCssFiles = [.../* @__PURE__ */ new Set([...cssFiles, ...cssRootFiles])];
|
|
242
|
+
result.css.push(...allCssFiles.map((f) => (0, import_path2.join)(projectPath, f)));
|
|
243
|
+
const imagePatterns = IMAGE_EXTENSIONS.flatMap((ext) => [`**/*${ext}`, `*${ext}`]);
|
|
244
|
+
const allImageFiles = /* @__PURE__ */ new Set();
|
|
245
|
+
for (const pattern of imagePatterns) {
|
|
246
|
+
const files = await (0, import_glob.glob)(pattern, {
|
|
247
|
+
cwd: projectPath,
|
|
248
|
+
ignore: IGNORED_PATTERNS,
|
|
249
|
+
absolute: false,
|
|
250
|
+
nodir: true
|
|
251
|
+
});
|
|
252
|
+
files.forEach((f) => allImageFiles.add(f));
|
|
253
|
+
}
|
|
254
|
+
result.images.push(...Array.from(allImageFiles).map((f) => (0, import_path2.join)(projectPath, f)));
|
|
255
|
+
const fontPatterns = FONT_EXTENSIONS.flatMap((ext) => [`**/*${ext}`, `*${ext}`]);
|
|
256
|
+
const allFontFiles = /* @__PURE__ */ new Set();
|
|
257
|
+
for (const pattern of fontPatterns) {
|
|
258
|
+
const files = await (0, import_glob.glob)(pattern, {
|
|
259
|
+
cwd: projectPath,
|
|
260
|
+
ignore: IGNORED_PATTERNS,
|
|
261
|
+
absolute: false,
|
|
262
|
+
nodir: true
|
|
263
|
+
});
|
|
264
|
+
files.forEach((f) => allFontFiles.add(f));
|
|
265
|
+
}
|
|
266
|
+
result.fonts.push(...Array.from(allFontFiles).map((f) => (0, import_path2.join)(projectPath, f)));
|
|
267
|
+
const routeFiles = await (0, import_glob.glob)(`**/*{route,api,graphql}*.{js,ts,json}`, {
|
|
268
|
+
cwd: projectPath,
|
|
269
|
+
ignore: IGNORED_PATTERNS,
|
|
270
|
+
absolute: false,
|
|
271
|
+
nodir: true
|
|
272
|
+
});
|
|
273
|
+
if (routeFiles.length > 0) {
|
|
274
|
+
result.apiRoutes.push(...API_PATTERNS);
|
|
275
|
+
}
|
|
276
|
+
result.javascript = result.javascript.filter((f) => {
|
|
277
|
+
try {
|
|
278
|
+
return (0, import_fs2.statSync)(f).isFile();
|
|
279
|
+
} catch {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
result.css = result.css.filter((f) => {
|
|
284
|
+
try {
|
|
285
|
+
return (0, import_fs2.statSync)(f).isFile();
|
|
286
|
+
} catch {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
result.images = result.images.filter((f) => {
|
|
291
|
+
try {
|
|
292
|
+
return (0, import_fs2.statSync)(f).isFile();
|
|
293
|
+
} catch {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
result.fonts = result.fonts.filter((f) => {
|
|
298
|
+
try {
|
|
299
|
+
return (0, import_fs2.statSync)(f).isFile();
|
|
300
|
+
} catch {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
return result;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// src/scanner/architecture-detector.ts
|
|
308
|
+
var import_fs3 = require("fs");
|
|
309
|
+
var import_path3 = require("path");
|
|
310
|
+
var import_glob2 = require("glob");
|
|
311
|
+
async function detectArchitecture(projectPath) {
|
|
312
|
+
const indicators = [];
|
|
313
|
+
let architecture = "static";
|
|
314
|
+
let buildTool = null;
|
|
315
|
+
let confidence = "low";
|
|
316
|
+
const packageJsonPath = (0, import_path3.join)(projectPath, "package.json");
|
|
317
|
+
if ((0, import_fs3.existsSync)(packageJsonPath)) {
|
|
318
|
+
try {
|
|
319
|
+
const packageContent = JSON.parse((0, import_fs3.readFileSync)(packageJsonPath, "utf-8"));
|
|
320
|
+
const dependencies = {
|
|
321
|
+
...packageContent.dependencies ?? {},
|
|
322
|
+
...packageContent.devDependencies ?? {}
|
|
323
|
+
};
|
|
324
|
+
if (dependencies.vite || packageContent.devDependencies?.["vite"]) {
|
|
325
|
+
indicators.push("package.json: vite");
|
|
326
|
+
buildTool = "vite";
|
|
327
|
+
confidence = "high";
|
|
328
|
+
} else if (dependencies.webpack || packageContent.devDependencies?.["webpack"]) {
|
|
329
|
+
indicators.push("package.json: webpack");
|
|
330
|
+
buildTool = "webpack";
|
|
331
|
+
confidence = "high";
|
|
332
|
+
} else if (dependencies.rollup || packageContent.devDependencies?.["rollup"]) {
|
|
333
|
+
indicators.push("package.json: rollup");
|
|
334
|
+
buildTool = "rollup";
|
|
335
|
+
confidence = "high";
|
|
336
|
+
} else if (dependencies.esbuild || packageContent.devDependencies?.["esbuild"]) {
|
|
337
|
+
indicators.push("package.json: esbuild");
|
|
338
|
+
buildTool = "esbuild";
|
|
339
|
+
confidence = "high";
|
|
340
|
+
} else if (dependencies.parcel || packageContent.devDependencies?.["parcel"]) {
|
|
341
|
+
indicators.push("package.json: parcel");
|
|
342
|
+
buildTool = "parcel";
|
|
343
|
+
confidence = "high";
|
|
344
|
+
} else if (dependencies["@turbo/gen"] || packageContent.devDependencies?.["@turbo/gen"]) {
|
|
345
|
+
indicators.push("package.json: turbopack");
|
|
346
|
+
buildTool = "turbopack";
|
|
347
|
+
confidence = "high";
|
|
348
|
+
}
|
|
349
|
+
if (dependencies.next || packageContent.dependencies?.["next"] || packageContent.devDependencies?.["next"]) {
|
|
350
|
+
architecture = "ssr";
|
|
351
|
+
confidence = "high";
|
|
352
|
+
indicators.push("Next.js detected \u2192 SSR");
|
|
353
|
+
} else if (dependencies.nuxt || packageContent.dependencies?.["nuxt"] || packageContent.devDependencies?.["nuxt"]) {
|
|
354
|
+
architecture = "ssr";
|
|
355
|
+
confidence = "high";
|
|
356
|
+
indicators.push("Nuxt detected \u2192 SSR");
|
|
357
|
+
}
|
|
358
|
+
} catch {
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const htmlPatterns = ["**/*.html", "*.html", "index.html"];
|
|
362
|
+
const allHtmlFiles = /* @__PURE__ */ new Set();
|
|
363
|
+
for (const pattern of htmlPatterns) {
|
|
364
|
+
const files = await (0, import_glob2.glob)(pattern, {
|
|
365
|
+
cwd: projectPath,
|
|
366
|
+
ignore: ["**/node_modules/**", "**/dist/**", "**/.next/**", "**/.nuxt/**"],
|
|
367
|
+
absolute: false,
|
|
368
|
+
nodir: true
|
|
369
|
+
});
|
|
370
|
+
files.forEach((f) => allHtmlFiles.add(f));
|
|
371
|
+
}
|
|
372
|
+
if (allHtmlFiles.size > 0 && architecture !== "ssr") {
|
|
373
|
+
const htmlArray = Array.from(allHtmlFiles);
|
|
374
|
+
const firstHtml = htmlArray.find((f) => f === "index.html" || f.endsWith("/index.html")) || htmlArray[0];
|
|
375
|
+
const htmlPath = (0, import_path3.join)(projectPath, firstHtml);
|
|
376
|
+
try {
|
|
377
|
+
const htmlContent = (0, import_fs3.readFileSync)(htmlPath, "utf-8").toLowerCase();
|
|
378
|
+
const spaPatterns = [
|
|
379
|
+
/<div[^>]*id=["']root["']/i,
|
|
380
|
+
/<div[^>]*id=["']app["']/i,
|
|
381
|
+
/<div[^>]*id=["']main["']/i,
|
|
382
|
+
/react-dom/i,
|
|
383
|
+
/mount\(/i,
|
|
384
|
+
/createRoot\(/i
|
|
385
|
+
];
|
|
386
|
+
const ssrPatterns = [
|
|
387
|
+
/<body[^>]*>[\s\S]{100,}/i,
|
|
388
|
+
// Body avec beaucoup de contenu
|
|
389
|
+
/<article/i,
|
|
390
|
+
/<main[^>]*>[\s\S]{50,}/i,
|
|
391
|
+
/hydrat/i,
|
|
392
|
+
/__next/i,
|
|
393
|
+
/__next_data__/i,
|
|
394
|
+
/nuxt/i
|
|
395
|
+
];
|
|
396
|
+
const hasSpaPattern = spaPatterns.some((pattern) => pattern.test(htmlContent));
|
|
397
|
+
const hasSsrPattern = ssrPatterns.some((pattern) => pattern.test(htmlContent));
|
|
398
|
+
const isLargeContent = htmlContent.length > 2e3;
|
|
399
|
+
const hasRealContent = htmlContent.replace(/<[^>]+>/g, "").trim().length > 100;
|
|
400
|
+
if (isLargeContent && hasRealContent && !hasSsrPattern) {
|
|
401
|
+
indicators.push("HTML: very large content with real text \u2192 SSR");
|
|
402
|
+
if (hasSpaPattern) {
|
|
403
|
+
indicators.push("HTML: SPA pattern but large content \u2192 SSR");
|
|
404
|
+
}
|
|
405
|
+
architecture = "ssr";
|
|
406
|
+
confidence = "high";
|
|
407
|
+
} else if (hasSsrPattern && htmlContent.length > 1e3) {
|
|
408
|
+
indicators.push("HTML: SSR patterns detected (large content)");
|
|
409
|
+
if (isLargeContent && hasRealContent) {
|
|
410
|
+
indicators.push("HTML: very large content with real text \u2192 SSR");
|
|
411
|
+
}
|
|
412
|
+
if (hasSpaPattern) {
|
|
413
|
+
indicators.push("HTML: SPA pattern but large content \u2192 SSR");
|
|
414
|
+
}
|
|
415
|
+
architecture = "ssr";
|
|
416
|
+
confidence = "high";
|
|
417
|
+
} else if (hasSsrPattern && !hasSpaPattern) {
|
|
418
|
+
indicators.push("HTML: SSR patterns detected");
|
|
419
|
+
if (htmlContent.length > 500) {
|
|
420
|
+
indicators.push("HTML: large content");
|
|
421
|
+
}
|
|
422
|
+
architecture = "ssr";
|
|
423
|
+
confidence = "high";
|
|
424
|
+
} else if (hasSpaPattern) {
|
|
425
|
+
if (isLargeContent && hasRealContent) {
|
|
426
|
+
indicators.push("HTML: SPA pattern but large content \u2192 SSR");
|
|
427
|
+
architecture = "ssr";
|
|
428
|
+
confidence = "medium";
|
|
429
|
+
} else {
|
|
430
|
+
indicators.push("HTML: SPA patterns detected");
|
|
431
|
+
architecture = "spa";
|
|
432
|
+
confidence = "high";
|
|
433
|
+
}
|
|
434
|
+
} else if (htmlContent.length > 500) {
|
|
435
|
+
indicators.push("HTML: large content");
|
|
436
|
+
architecture = "ssr";
|
|
437
|
+
confidence = "medium";
|
|
438
|
+
} else {
|
|
439
|
+
indicators.push("HTML: minimal content");
|
|
440
|
+
architecture = "static";
|
|
441
|
+
confidence = "medium";
|
|
442
|
+
}
|
|
443
|
+
} catch {
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
const jsFiles = await (0, import_glob2.glob)("**/*.{js,ts,tsx,jsx}", {
|
|
447
|
+
cwd: projectPath,
|
|
448
|
+
ignore: ["**/node_modules/**", "**/dist/**", "**/.next/**", "**/.nuxt/**", "**/*.test.*", "**/*.spec.*"],
|
|
449
|
+
absolute: false,
|
|
450
|
+
nodir: true
|
|
451
|
+
});
|
|
452
|
+
if (jsFiles.length > 0 && architecture === "static") {
|
|
453
|
+
let routerFilesFound = 0;
|
|
454
|
+
for (const jsFile of jsFiles.slice(0, 10)) {
|
|
455
|
+
try {
|
|
456
|
+
const jsPath = (0, import_path3.join)(projectPath, jsFile);
|
|
457
|
+
const jsContent = (0, import_fs3.readFileSync)(jsPath, "utf-8").toLowerCase();
|
|
458
|
+
const routerPatterns = [
|
|
459
|
+
/react-router/i,
|
|
460
|
+
/vue-router/i,
|
|
461
|
+
/@angular\/router/i,
|
|
462
|
+
/next\/router/i,
|
|
463
|
+
/nuxt/i,
|
|
464
|
+
/createBrowserRouter/i,
|
|
465
|
+
/BrowserRouter/i,
|
|
466
|
+
/Router/i
|
|
467
|
+
];
|
|
468
|
+
if (routerPatterns.some((pattern) => pattern.test(jsContent))) {
|
|
469
|
+
routerFilesFound++;
|
|
470
|
+
}
|
|
471
|
+
} catch {
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (routerFilesFound > 0) {
|
|
475
|
+
indicators.push(`JS: router patterns found (${routerFilesFound} files)`);
|
|
476
|
+
if (architecture === "static") {
|
|
477
|
+
architecture = "spa";
|
|
478
|
+
confidence = confidence === "low" ? "medium" : confidence;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return { architecture, buildTool, confidence, indicators };
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// src/scanner/index.ts
|
|
486
|
+
async function scanProject(options) {
|
|
487
|
+
const { projectPath, includeAssets = true, includeArchitecture = true } = options;
|
|
488
|
+
const frameworkCandidate = detectFramework(projectPath);
|
|
489
|
+
const framework = isFrameworkDetectionResult(frameworkCandidate) ? frameworkCandidate : { framework: null, confidence: "low", indicators: [] };
|
|
490
|
+
const assetsCandidate = includeAssets ? await detectAssets(projectPath) : getEmptyAssets();
|
|
491
|
+
const assets = isAssetDetectionResult(assetsCandidate) ? assetsCandidate : getEmptyAssets();
|
|
492
|
+
const architectureCandidate = includeArchitecture ? await detectArchitecture(projectPath) : getEmptyArchitecture();
|
|
493
|
+
const architecture = isArchitectureDetectionResult(architectureCandidate) ? architectureCandidate : getEmptyArchitecture();
|
|
494
|
+
return {
|
|
495
|
+
framework,
|
|
496
|
+
assets,
|
|
497
|
+
architecture,
|
|
498
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
499
|
+
projectPath
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
function generateReport(result) {
|
|
503
|
+
return JSON.stringify(result, null, 2);
|
|
504
|
+
}
|
|
505
|
+
function validateProjectPath(projectPath) {
|
|
506
|
+
try {
|
|
507
|
+
return (0, import_fs4.existsSync)(projectPath);
|
|
508
|
+
} catch {
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
function isFrameworkDetectionResult(value) {
|
|
513
|
+
if (!value || typeof value !== "object") return false;
|
|
514
|
+
const v = value;
|
|
515
|
+
return (v.framework === null || typeof v.framework === "string") && (v.confidence === "low" || v.confidence === "medium" || v.confidence === "high") && Array.isArray(v.indicators);
|
|
516
|
+
}
|
|
517
|
+
function isAssetDetectionResult(value) {
|
|
518
|
+
if (!value || typeof value !== "object") return false;
|
|
519
|
+
const v = value;
|
|
520
|
+
const isStringArray = (x) => Array.isArray(x) && x.every((i) => typeof i === "string");
|
|
521
|
+
return isStringArray(v.javascript) && isStringArray(v.css) && isStringArray(v.images) && isStringArray(v.fonts) && isStringArray(v.apiRoutes);
|
|
522
|
+
}
|
|
523
|
+
function isArchitectureDetectionResult(value) {
|
|
524
|
+
if (!value || typeof value !== "object") return false;
|
|
525
|
+
const v = value;
|
|
526
|
+
const isArch = v.architecture === "spa" || v.architecture === "ssr" || v.architecture === "static";
|
|
527
|
+
const isBuildTool = v.buildTool === null || v.buildTool === "vite" || v.buildTool === "webpack" || v.buildTool === "rollup";
|
|
528
|
+
const isConfidence = v.confidence === "low" || v.confidence === "medium" || v.confidence === "high";
|
|
529
|
+
return isArch && isBuildTool && isConfidence && Array.isArray(v.indicators);
|
|
530
|
+
}
|
|
531
|
+
function getEmptyAssets() {
|
|
532
|
+
return {
|
|
533
|
+
javascript: [],
|
|
534
|
+
css: [],
|
|
535
|
+
images: [],
|
|
536
|
+
fonts: [],
|
|
537
|
+
apiRoutes: []
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
function getEmptyArchitecture() {
|
|
541
|
+
return {
|
|
542
|
+
architecture: "static",
|
|
543
|
+
buildTool: null,
|
|
544
|
+
confidence: "low",
|
|
545
|
+
indicators: []
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// src/generator/manifest-generator.ts
|
|
550
|
+
var import_zod = require("zod");
|
|
551
|
+
var import_fs5 = require("fs");
|
|
552
|
+
var import_path4 = require("path");
|
|
553
|
+
var ManifestIconSchema = import_zod.z.object({
|
|
554
|
+
src: import_zod.z.string(),
|
|
555
|
+
sizes: import_zod.z.string(),
|
|
556
|
+
type: import_zod.z.string().optional(),
|
|
557
|
+
purpose: import_zod.z.string().optional()
|
|
558
|
+
});
|
|
559
|
+
var ManifestSplashScreenSchema = import_zod.z.object({
|
|
560
|
+
src: import_zod.z.string(),
|
|
561
|
+
sizes: import_zod.z.string(),
|
|
562
|
+
type: import_zod.z.string().optional()
|
|
563
|
+
});
|
|
564
|
+
var ManifestSchema = import_zod.z.object({
|
|
565
|
+
name: import_zod.z.string().min(1),
|
|
566
|
+
short_name: import_zod.z.string().min(1).max(12),
|
|
567
|
+
description: import_zod.z.string().optional(),
|
|
568
|
+
start_url: import_zod.z.string().default("/"),
|
|
569
|
+
scope: import_zod.z.string().default("/"),
|
|
570
|
+
display: import_zod.z.enum(["standalone", "fullscreen", "minimal-ui", "browser"]).default("standalone"),
|
|
571
|
+
orientation: import_zod.z.enum(["any", "portrait", "landscape"]).optional(),
|
|
572
|
+
theme_color: import_zod.z.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(),
|
|
573
|
+
background_color: import_zod.z.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(),
|
|
574
|
+
icons: import_zod.z.array(ManifestIconSchema).min(1),
|
|
575
|
+
splash_screens: import_zod.z.array(ManifestSplashScreenSchema).optional(),
|
|
576
|
+
categories: import_zod.z.array(import_zod.z.string()).optional(),
|
|
577
|
+
lang: import_zod.z.string().optional(),
|
|
578
|
+
dir: import_zod.z.enum(["ltr", "rtl", "auto"]).optional(),
|
|
579
|
+
prefer_related_applications: import_zod.z.boolean().optional(),
|
|
580
|
+
related_applications: import_zod.z.array(import_zod.z.unknown()).optional()
|
|
581
|
+
});
|
|
582
|
+
function generateManifest(options) {
|
|
583
|
+
const manifest = {
|
|
584
|
+
name: options.name,
|
|
585
|
+
short_name: options.shortName,
|
|
586
|
+
start_url: options.startUrl ?? "/",
|
|
587
|
+
scope: options.scope ?? "/",
|
|
588
|
+
display: options.display ?? "standalone",
|
|
589
|
+
icons: options.icons
|
|
590
|
+
};
|
|
591
|
+
if (options.description) {
|
|
592
|
+
manifest.description = options.description;
|
|
593
|
+
}
|
|
594
|
+
if (options.orientation) {
|
|
595
|
+
manifest.orientation = options.orientation;
|
|
596
|
+
}
|
|
597
|
+
if (options.themeColor) {
|
|
598
|
+
manifest.theme_color = options.themeColor;
|
|
599
|
+
}
|
|
600
|
+
if (options.backgroundColor) {
|
|
601
|
+
manifest.background_color = options.backgroundColor;
|
|
602
|
+
}
|
|
603
|
+
if (options.splashScreens && options.splashScreens.length > 0) {
|
|
604
|
+
manifest.splash_screens = options.splashScreens;
|
|
605
|
+
}
|
|
606
|
+
if (options.categories && options.categories.length > 0) {
|
|
607
|
+
manifest.categories = options.categories;
|
|
608
|
+
}
|
|
609
|
+
if (options.lang) {
|
|
610
|
+
manifest.lang = options.lang;
|
|
611
|
+
}
|
|
612
|
+
if (options.dir) {
|
|
613
|
+
manifest.dir = options.dir;
|
|
614
|
+
}
|
|
615
|
+
if (options.preferRelatedApplications !== void 0) {
|
|
616
|
+
manifest.prefer_related_applications = options.preferRelatedApplications;
|
|
617
|
+
}
|
|
618
|
+
if (options.relatedApplications && options.relatedApplications.length > 0) {
|
|
619
|
+
manifest.related_applications = options.relatedApplications;
|
|
620
|
+
}
|
|
621
|
+
return ManifestSchema.parse(manifest);
|
|
622
|
+
}
|
|
623
|
+
function writeManifest(manifest, outputDir) {
|
|
624
|
+
const manifestPath = (0, import_path4.join)(outputDir, "manifest.json");
|
|
625
|
+
const manifestJson = JSON.stringify(manifest, null, 2);
|
|
626
|
+
(0, import_fs5.writeFileSync)(manifestPath, manifestJson, "utf-8");
|
|
627
|
+
return manifestPath;
|
|
628
|
+
}
|
|
629
|
+
function generateAndWriteManifest(options, outputDir) {
|
|
630
|
+
const manifest = generateManifest(options);
|
|
631
|
+
return writeManifest(manifest, outputDir);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/generator/icon-generator.ts
|
|
635
|
+
var import_sharp = __toESM(require("sharp"), 1);
|
|
636
|
+
var import_fs6 = require("fs");
|
|
637
|
+
var import_path5 = require("path");
|
|
638
|
+
var STANDARD_ICON_SIZES = [
|
|
639
|
+
{ width: 72, height: 72, name: "icon-72x72.png" },
|
|
640
|
+
{ width: 96, height: 96, name: "icon-96x96.png" },
|
|
641
|
+
{ width: 128, height: 128, name: "icon-128x128.png" },
|
|
642
|
+
{ width: 144, height: 144, name: "icon-144x144.png" },
|
|
643
|
+
{ width: 152, height: 152, name: "icon-152x152.png" },
|
|
644
|
+
{ width: 192, height: 192, name: "icon-192x192.png" },
|
|
645
|
+
{ width: 384, height: 384, name: "icon-384x384.png" },
|
|
646
|
+
{ width: 512, height: 512, name: "icon-512x512.png" }
|
|
647
|
+
];
|
|
648
|
+
var STANDARD_SPLASH_SIZES = [
|
|
649
|
+
{ width: 640, height: 1136, name: "splash-640x1136.png" },
|
|
650
|
+
// iPhone 5
|
|
651
|
+
{ width: 750, height: 1334, name: "splash-750x1334.png" },
|
|
652
|
+
// iPhone 6/7/8
|
|
653
|
+
{ width: 828, height: 1792, name: "splash-828x1792.png" },
|
|
654
|
+
// iPhone XR
|
|
655
|
+
{ width: 1125, height: 2436, name: "splash-1125x2436.png" },
|
|
656
|
+
// iPhone X/XS
|
|
657
|
+
{ width: 1242, height: 2688, name: "splash-1242x2688.png" },
|
|
658
|
+
// iPhone XS Max
|
|
659
|
+
{ width: 1536, height: 2048, name: "splash-1536x2048.png" },
|
|
660
|
+
// iPad
|
|
661
|
+
{ width: 2048, height: 2732, name: "splash-2048x2732.png" }
|
|
662
|
+
// iPad Pro
|
|
663
|
+
];
|
|
664
|
+
async function generateIcons(options) {
|
|
665
|
+
const {
|
|
666
|
+
sourceImage,
|
|
667
|
+
outputDir,
|
|
668
|
+
iconSizes = STANDARD_ICON_SIZES,
|
|
669
|
+
splashSizes = STANDARD_SPLASH_SIZES,
|
|
670
|
+
format = "png",
|
|
671
|
+
quality = 90
|
|
672
|
+
} = options;
|
|
673
|
+
if (!(0, import_fs6.existsSync)(sourceImage)) {
|
|
674
|
+
throw new Error(`Source image not found: ${sourceImage}`);
|
|
675
|
+
}
|
|
676
|
+
(0, import_fs6.mkdirSync)(outputDir, { recursive: true });
|
|
677
|
+
const generatedFiles = [];
|
|
678
|
+
const icons = [];
|
|
679
|
+
const splashScreens = [];
|
|
680
|
+
const image = (0, import_sharp.default)(sourceImage);
|
|
681
|
+
const metadata = await image.metadata();
|
|
682
|
+
if (!metadata.width || !metadata.height) {
|
|
683
|
+
throw new Error("Unable to read image dimensions");
|
|
684
|
+
}
|
|
685
|
+
for (const size of iconSizes) {
|
|
686
|
+
const outputPath = (0, import_path5.join)(outputDir, size.name);
|
|
687
|
+
try {
|
|
688
|
+
let pipeline = image.clone().resize(size.width, size.height, {
|
|
689
|
+
fit: "cover",
|
|
690
|
+
position: "center"
|
|
691
|
+
});
|
|
692
|
+
if (format === "png") {
|
|
693
|
+
pipeline = pipeline.png({ quality, compressionLevel: 9 });
|
|
694
|
+
} else {
|
|
695
|
+
pipeline = pipeline.webp({ quality });
|
|
696
|
+
}
|
|
697
|
+
await pipeline.toFile(outputPath);
|
|
698
|
+
generatedFiles.push(outputPath);
|
|
699
|
+
icons.push({
|
|
700
|
+
src: `/${size.name}`,
|
|
701
|
+
sizes: `${size.width}x${size.height}`,
|
|
702
|
+
type: format === "png" ? "image/png" : "image/webp",
|
|
703
|
+
purpose: size.width >= 192 && size.width <= 512 ? "any" : void 0
|
|
704
|
+
});
|
|
705
|
+
} catch (err) {
|
|
706
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
707
|
+
throw new Error(`Failed to generate icon ${size.name}: ${message}`);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
for (const size of splashSizes) {
|
|
711
|
+
const outputPath = (0, import_path5.join)(outputDir, size.name);
|
|
712
|
+
try {
|
|
713
|
+
let pipeline = image.clone().resize(size.width, size.height, {
|
|
714
|
+
fit: "cover",
|
|
715
|
+
position: "center"
|
|
716
|
+
});
|
|
717
|
+
if (format === "png") {
|
|
718
|
+
pipeline = pipeline.png({ quality, compressionLevel: 9 });
|
|
719
|
+
} else {
|
|
720
|
+
pipeline = pipeline.webp({ quality });
|
|
721
|
+
}
|
|
722
|
+
await pipeline.toFile(outputPath);
|
|
723
|
+
generatedFiles.push(outputPath);
|
|
724
|
+
splashScreens.push({
|
|
725
|
+
src: `/${size.name}`,
|
|
726
|
+
sizes: `${size.width}x${size.height}`,
|
|
727
|
+
type: format === "png" ? "image/png" : "image/webp"
|
|
728
|
+
});
|
|
729
|
+
} catch (err) {
|
|
730
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
731
|
+
throw new Error(`Failed to generate splash screen ${size.name}: ${message}`);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return {
|
|
735
|
+
icons,
|
|
736
|
+
splashScreens,
|
|
737
|
+
generatedFiles
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
async function generateIconsOnly(options) {
|
|
741
|
+
const result = await generateIcons({
|
|
742
|
+
...options,
|
|
743
|
+
splashSizes: []
|
|
744
|
+
});
|
|
745
|
+
return {
|
|
746
|
+
icons: result.icons,
|
|
747
|
+
generatedFiles: result.generatedFiles
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
async function generateSplashScreensOnly(options) {
|
|
751
|
+
const result = await generateIcons({
|
|
752
|
+
...options,
|
|
753
|
+
iconSizes: []
|
|
754
|
+
});
|
|
755
|
+
return {
|
|
756
|
+
splashScreens: result.splashScreens,
|
|
757
|
+
generatedFiles: result.generatedFiles
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
async function generateFavicon(sourceImage, outputDir) {
|
|
761
|
+
if (!(0, import_fs6.existsSync)(sourceImage)) {
|
|
762
|
+
throw new Error(`Source image not found: ${sourceImage}`);
|
|
763
|
+
}
|
|
764
|
+
(0, import_fs6.mkdirSync)(outputDir, { recursive: true });
|
|
765
|
+
const faviconPath = (0, import_path5.join)(outputDir, "favicon.ico");
|
|
766
|
+
try {
|
|
767
|
+
await (0, import_sharp.default)(sourceImage).resize(32, 32, {
|
|
768
|
+
fit: "cover",
|
|
769
|
+
position: "center"
|
|
770
|
+
}).png().toFile(faviconPath);
|
|
771
|
+
return faviconPath;
|
|
772
|
+
} catch (err) {
|
|
773
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
774
|
+
throw new Error(`Failed to generate favicon: ${message}`);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
async function generateAppleTouchIcon(sourceImage, outputDir) {
|
|
778
|
+
if (!(0, import_fs6.existsSync)(sourceImage)) {
|
|
779
|
+
throw new Error(`Source image not found: ${sourceImage}`);
|
|
780
|
+
}
|
|
781
|
+
(0, import_fs6.mkdirSync)(outputDir, { recursive: true });
|
|
782
|
+
const appleIconPath = (0, import_path5.join)(outputDir, "apple-touch-icon.png");
|
|
783
|
+
try {
|
|
784
|
+
await (0, import_sharp.default)(sourceImage).resize(180, 180, {
|
|
785
|
+
fit: "cover",
|
|
786
|
+
position: "center"
|
|
787
|
+
}).png({ quality: 90, compressionLevel: 9 }).toFile(appleIconPath);
|
|
788
|
+
return appleIconPath;
|
|
789
|
+
} catch (err) {
|
|
790
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
791
|
+
throw new Error(`Failed to generate apple-touch-icon: ${message}`);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// src/generator/service-worker-generator.ts
|
|
796
|
+
var import_workbox_build = require("workbox-build");
|
|
797
|
+
var import_fs7 = require("fs");
|
|
798
|
+
var import_path6 = require("path");
|
|
799
|
+
var import_templates = require("@universal-pwa/templates");
|
|
800
|
+
async function generateServiceWorker(options) {
|
|
801
|
+
const {
|
|
802
|
+
projectPath,
|
|
803
|
+
outputDir,
|
|
804
|
+
architecture,
|
|
805
|
+
framework,
|
|
806
|
+
templateType,
|
|
807
|
+
globDirectory,
|
|
808
|
+
globPatterns = ["**/*.{js,css,html,png,jpg,jpeg,svg,webp,woff,woff2,ttf,otf}"],
|
|
809
|
+
swDest = "sw.js",
|
|
810
|
+
offlinePage
|
|
811
|
+
} = options;
|
|
812
|
+
(0, import_fs7.mkdirSync)(outputDir, { recursive: true });
|
|
813
|
+
const finalTemplateType = templateType ?? (0, import_templates.determineTemplateType)(architecture, framework ?? null);
|
|
814
|
+
const template = (0, import_templates.getServiceWorkerTemplate)(finalTemplateType);
|
|
815
|
+
const swSrcPath = (0, import_path6.join)(outputDir, "sw-src.js");
|
|
816
|
+
(0, import_fs7.writeFileSync)(swSrcPath, template.content, "utf-8");
|
|
817
|
+
const swDestPath = (0, import_path6.join)(outputDir, swDest);
|
|
818
|
+
const workboxConfig = {
|
|
819
|
+
globDirectory: globDirectory ?? projectPath,
|
|
820
|
+
globPatterns,
|
|
821
|
+
swDest: swDestPath,
|
|
822
|
+
swSrc: swSrcPath,
|
|
823
|
+
// Injection du manifest dans le template
|
|
824
|
+
injectionPoint: "self.__WB_MANIFEST"
|
|
825
|
+
};
|
|
826
|
+
if (offlinePage) {
|
|
827
|
+
workboxConfig.globPatterns = [...workboxConfig.globPatterns ?? [], offlinePage];
|
|
828
|
+
}
|
|
829
|
+
try {
|
|
830
|
+
const result = await (0, import_workbox_build.injectManifest)(workboxConfig);
|
|
831
|
+
try {
|
|
832
|
+
if ((0, import_fs7.existsSync)(swSrcPath)) {
|
|
833
|
+
}
|
|
834
|
+
} catch {
|
|
835
|
+
}
|
|
836
|
+
const resultFilePaths = result.filePaths ?? [];
|
|
837
|
+
const normalizedOffline = offlinePage ? offlinePage.replace(/^\.\//, "") : void 0;
|
|
838
|
+
const finalFilePaths = normalizedOffline && !resultFilePaths.some((p) => p.includes(normalizedOffline)) ? [...resultFilePaths, normalizedOffline] : resultFilePaths;
|
|
839
|
+
return {
|
|
840
|
+
swPath: swDestPath,
|
|
841
|
+
count: result.count,
|
|
842
|
+
size: result.size,
|
|
843
|
+
warnings: result.warnings ?? [],
|
|
844
|
+
filePaths: finalFilePaths
|
|
845
|
+
};
|
|
846
|
+
} catch (err) {
|
|
847
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
848
|
+
throw new Error(`Failed to generate service worker: ${message}`);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
async function generateSimpleServiceWorker(options) {
|
|
852
|
+
const {
|
|
853
|
+
projectPath,
|
|
854
|
+
outputDir,
|
|
855
|
+
globDirectory,
|
|
856
|
+
globPatterns = ["**/*.{js,css,html,png,jpg,jpeg,svg,webp,woff,woff2,ttf,otf}"],
|
|
857
|
+
swDest = "sw.js",
|
|
858
|
+
skipWaiting = true,
|
|
859
|
+
clientsClaim = true,
|
|
860
|
+
runtimeCaching
|
|
861
|
+
} = options;
|
|
862
|
+
(0, import_fs7.mkdirSync)(outputDir, { recursive: true });
|
|
863
|
+
const swDestPath = (0, import_path6.join)(outputDir, swDest);
|
|
864
|
+
const workboxConfig = {
|
|
865
|
+
globDirectory: globDirectory ?? projectPath,
|
|
866
|
+
globPatterns,
|
|
867
|
+
swDest: swDestPath,
|
|
868
|
+
skipWaiting,
|
|
869
|
+
clientsClaim,
|
|
870
|
+
mode: "production",
|
|
871
|
+
sourcemap: false
|
|
872
|
+
};
|
|
873
|
+
if (runtimeCaching && runtimeCaching.length > 0) {
|
|
874
|
+
workboxConfig.runtimeCaching = runtimeCaching.map((cache) => {
|
|
875
|
+
const handlerMap = {
|
|
876
|
+
NetworkFirst: "NetworkFirst",
|
|
877
|
+
CacheFirst: "CacheFirst",
|
|
878
|
+
StaleWhileRevalidate: "StaleWhileRevalidate",
|
|
879
|
+
NetworkOnly: "NetworkOnly",
|
|
880
|
+
CacheOnly: "CacheOnly"
|
|
881
|
+
};
|
|
882
|
+
const handler = handlerMap[cache.handler] ?? cache.handler;
|
|
883
|
+
return {
|
|
884
|
+
urlPattern: typeof cache.urlPattern === "string" ? new RegExp(cache.urlPattern) : cache.urlPattern,
|
|
885
|
+
handler,
|
|
886
|
+
options: cache.options ? {
|
|
887
|
+
cacheName: cache.options.cacheName,
|
|
888
|
+
expiration: cache.options.expiration ? {
|
|
889
|
+
maxEntries: cache.options.expiration.maxEntries,
|
|
890
|
+
maxAgeSeconds: cache.options.expiration.maxAgeSeconds
|
|
891
|
+
} : void 0
|
|
892
|
+
} : void 0
|
|
893
|
+
};
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
try {
|
|
897
|
+
const result = await (0, import_workbox_build.generateSW)(workboxConfig);
|
|
898
|
+
return {
|
|
899
|
+
swPath: swDestPath,
|
|
900
|
+
count: result.count,
|
|
901
|
+
size: result.size,
|
|
902
|
+
warnings: result.warnings ?? [],
|
|
903
|
+
filePaths: (result.manifestEntries ?? []).map(
|
|
904
|
+
(entry) => typeof entry === "string" ? entry : entry.url
|
|
905
|
+
)
|
|
906
|
+
};
|
|
907
|
+
} catch (err) {
|
|
908
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
909
|
+
throw new Error(`Failed to generate service worker: ${message}`);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
async function generateAndWriteServiceWorker(options) {
|
|
913
|
+
return generateServiceWorker(options);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// src/generator/https-checker.ts
|
|
917
|
+
var import_fs8 = require("fs");
|
|
918
|
+
var import_path7 = require("path");
|
|
919
|
+
function checkHttps(url, allowHttpLocalhost = true) {
|
|
920
|
+
let parsedUrl;
|
|
921
|
+
try {
|
|
922
|
+
parsedUrl = new URL(url);
|
|
923
|
+
} catch {
|
|
924
|
+
return {
|
|
925
|
+
isSecure: false,
|
|
926
|
+
isLocalhost: false,
|
|
927
|
+
isProduction: false,
|
|
928
|
+
protocol: "unknown",
|
|
929
|
+
hostname: null,
|
|
930
|
+
warning: "Invalid URL format",
|
|
931
|
+
recommendation: "Provide a valid URL (e.g., https://example.com or http://localhost:3000)"
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
const protocol = parsedUrl.protocol.replace(":", "");
|
|
935
|
+
const hostname = parsedUrl.hostname;
|
|
936
|
+
const isHttps = protocol === "https";
|
|
937
|
+
const isLocalhost = hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname.startsWith("192.168.") || hostname.startsWith("10.") || hostname.startsWith("172.16.") || hostname.endsWith(".local");
|
|
938
|
+
const isSecure = isHttps || isLocalhost && allowHttpLocalhost;
|
|
939
|
+
const isProduction = isHttps && !isLocalhost;
|
|
940
|
+
let warning;
|
|
941
|
+
let recommendation;
|
|
942
|
+
if (!isSecure) {
|
|
943
|
+
if (isLocalhost && !allowHttpLocalhost) {
|
|
944
|
+
warning = "Service Workers require HTTPS in production. HTTP is only allowed on localhost for development.";
|
|
945
|
+
recommendation = "Use HTTPS in production or enable allowHttpLocalhost option for development.";
|
|
946
|
+
} else if (!isLocalhost) {
|
|
947
|
+
warning = "Service Workers require HTTPS in production. HTTP is not secure and will not work.";
|
|
948
|
+
recommendation = "Deploy your PWA with HTTPS. Consider using services like Vercel, Netlify, or Cloudflare that provide HTTPS by default.";
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
return {
|
|
952
|
+
isSecure,
|
|
953
|
+
isLocalhost,
|
|
954
|
+
isProduction,
|
|
955
|
+
protocol,
|
|
956
|
+
hostname,
|
|
957
|
+
warning,
|
|
958
|
+
recommendation
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
function detectProjectUrl(projectPath) {
|
|
962
|
+
const configFiles = [
|
|
963
|
+
{ file: "package.json", key: "homepage" },
|
|
964
|
+
{ file: "package.json", key: "url" },
|
|
965
|
+
{ file: ".env", pattern: /^.*URL.*=(.+)$/i },
|
|
966
|
+
{ file: ".env.local", pattern: /^.*URL.*=(.+)$/i },
|
|
967
|
+
{ file: "vercel.json", key: "url" },
|
|
968
|
+
{ file: "netlify.toml", pattern: /^.*url.*=.*["'](.+)["']/i },
|
|
969
|
+
{ file: "next.config.js", pattern: /baseUrl.*["'](.+)["']/ },
|
|
970
|
+
{ file: "next.config.ts", pattern: /baseUrl.*["'](.+)["']/ }
|
|
971
|
+
];
|
|
972
|
+
for (const config of configFiles) {
|
|
973
|
+
const filePath = (0, import_path7.join)(projectPath, config.file);
|
|
974
|
+
if ((0, import_fs8.existsSync)(filePath)) {
|
|
975
|
+
try {
|
|
976
|
+
if (config.file.endsWith(".json") && config.key) {
|
|
977
|
+
const parsed = JSON.parse((0, import_fs8.readFileSync)(filePath, "utf-8"));
|
|
978
|
+
const content = parsed;
|
|
979
|
+
const value = content[config.key];
|
|
980
|
+
if (value && typeof value === "string") {
|
|
981
|
+
return value;
|
|
982
|
+
}
|
|
983
|
+
} else if (config.file.endsWith(".toml") && config.pattern) {
|
|
984
|
+
const content = (0, import_fs8.readFileSync)(filePath, "utf-8");
|
|
985
|
+
const match = config.pattern ? content.match(config.pattern) : null;
|
|
986
|
+
if (match && match[1]) {
|
|
987
|
+
return match[1];
|
|
988
|
+
}
|
|
989
|
+
} else if ((config.file.endsWith(".js") || config.file.endsWith(".ts")) && config.pattern) {
|
|
990
|
+
const content = (0, import_fs8.readFileSync)(filePath, "utf-8");
|
|
991
|
+
const match = config.pattern ? content.match(config.pattern) : null;
|
|
992
|
+
if (match && match[1]) {
|
|
993
|
+
return match[1];
|
|
994
|
+
}
|
|
995
|
+
} else if (config.file.startsWith(".env") && config.pattern) {
|
|
996
|
+
const content = (0, import_fs8.readFileSync)(filePath, "utf-8");
|
|
997
|
+
const lines = content.split("\n");
|
|
998
|
+
for (const line of lines) {
|
|
999
|
+
const match = config.pattern ? line.match(config.pattern) : null;
|
|
1000
|
+
if (match && match[1]) {
|
|
1001
|
+
return match[1].trim();
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
} catch {
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
return null;
|
|
1010
|
+
}
|
|
1011
|
+
function checkProjectHttps(options = {}) {
|
|
1012
|
+
const { url, projectPath, allowHttpLocalhost = true } = options;
|
|
1013
|
+
if (url) {
|
|
1014
|
+
return checkHttps(url, allowHttpLocalhost);
|
|
1015
|
+
}
|
|
1016
|
+
if (projectPath) {
|
|
1017
|
+
const detectedUrl = detectProjectUrl(projectPath);
|
|
1018
|
+
if (detectedUrl) {
|
|
1019
|
+
return checkHttps(detectedUrl, allowHttpLocalhost);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
return {
|
|
1023
|
+
isSecure: false,
|
|
1024
|
+
isLocalhost: false,
|
|
1025
|
+
isProduction: false,
|
|
1026
|
+
protocol: "unknown",
|
|
1027
|
+
hostname: null,
|
|
1028
|
+
warning: "Unable to determine project URL. HTTPS check cannot be performed.",
|
|
1029
|
+
recommendation: "Provide a URL explicitly or ensure your project has a valid configuration file (package.json, .env, etc.)."
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// src/injector/html-parser.ts
|
|
1034
|
+
var import_htmlparser2 = require("htmlparser2");
|
|
1035
|
+
var import_fs9 = require("fs");
|
|
1036
|
+
function parseHTMLFile(filePath, options = {}) {
|
|
1037
|
+
const content = (0, import_fs9.readFileSync)(filePath, "utf-8");
|
|
1038
|
+
return parseHTML(content, options);
|
|
1039
|
+
}
|
|
1040
|
+
function parseHTML(htmlContent, options = {}) {
|
|
1041
|
+
const {
|
|
1042
|
+
decodeEntities = true,
|
|
1043
|
+
lowerCaseAttributeNames = true
|
|
1044
|
+
} = options;
|
|
1045
|
+
const document = (0, import_htmlparser2.parseDocument)(htmlContent, {
|
|
1046
|
+
decodeEntities,
|
|
1047
|
+
lowerCaseAttributeNames,
|
|
1048
|
+
lowerCaseTags: true
|
|
1049
|
+
});
|
|
1050
|
+
let head = null;
|
|
1051
|
+
let body = null;
|
|
1052
|
+
let html = null;
|
|
1053
|
+
const findElements = (node) => {
|
|
1054
|
+
if (node.type === "tag") {
|
|
1055
|
+
const element = node;
|
|
1056
|
+
const tagName = element.tagName.toLowerCase();
|
|
1057
|
+
if (tagName === "head" && !head) {
|
|
1058
|
+
head = element;
|
|
1059
|
+
} else if (tagName === "body" && !body) {
|
|
1060
|
+
body = element;
|
|
1061
|
+
} else if (tagName === "html" && !html) {
|
|
1062
|
+
html = element;
|
|
1063
|
+
}
|
|
1064
|
+
if (element.children) {
|
|
1065
|
+
for (const child of element.children) {
|
|
1066
|
+
findElements(child);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
};
|
|
1071
|
+
if (document.children) {
|
|
1072
|
+
for (const child of document.children) {
|
|
1073
|
+
findElements(child);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
return {
|
|
1077
|
+
document,
|
|
1078
|
+
head,
|
|
1079
|
+
body,
|
|
1080
|
+
html,
|
|
1081
|
+
originalContent: htmlContent
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
function findElement(parsed, tagName, attribute) {
|
|
1085
|
+
const searchIn = parsed.head || parsed.document;
|
|
1086
|
+
const search = (node) => {
|
|
1087
|
+
if (node.type === "tag") {
|
|
1088
|
+
const element = node;
|
|
1089
|
+
if (element.tagName.toLowerCase() === tagName.toLowerCase()) {
|
|
1090
|
+
if (!attribute) {
|
|
1091
|
+
return element;
|
|
1092
|
+
}
|
|
1093
|
+
const attrValue = element.attributes?.find((attr) => attr.name.toLowerCase() === attribute.name.toLowerCase())?.value;
|
|
1094
|
+
if (attrValue === attribute.value) {
|
|
1095
|
+
return element;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
if (element.children) {
|
|
1099
|
+
for (const child of element.children) {
|
|
1100
|
+
const found = search(child);
|
|
1101
|
+
if (found) {
|
|
1102
|
+
return found;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
return null;
|
|
1108
|
+
};
|
|
1109
|
+
if (searchIn) {
|
|
1110
|
+
if (searchIn.type === "tag") {
|
|
1111
|
+
return search(searchIn);
|
|
1112
|
+
}
|
|
1113
|
+
if ("children" in searchIn && searchIn.children) {
|
|
1114
|
+
for (const child of searchIn.children) {
|
|
1115
|
+
const found = search(child);
|
|
1116
|
+
if (found) {
|
|
1117
|
+
return found;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
return null;
|
|
1123
|
+
}
|
|
1124
|
+
function findAllElements(parsed, tagName, attribute) {
|
|
1125
|
+
const results = [];
|
|
1126
|
+
const targetTagName = tagName.toLowerCase();
|
|
1127
|
+
const search = (node, isRoot = false) => {
|
|
1128
|
+
if (node.type === "tag") {
|
|
1129
|
+
const element = node;
|
|
1130
|
+
const nodeTagName = element.tagName.toLowerCase();
|
|
1131
|
+
if (nodeTagName === targetTagName && (!isRoot || targetTagName === "html" || targetTagName === "head" || targetTagName === "body")) {
|
|
1132
|
+
if (!attribute) {
|
|
1133
|
+
results.push(element);
|
|
1134
|
+
} else {
|
|
1135
|
+
const attrValue = element.attributes?.find((attr) => attr.name.toLowerCase() === attribute.name.toLowerCase())?.value;
|
|
1136
|
+
if (attribute.value === void 0 || attrValue === attribute.value) {
|
|
1137
|
+
results.push(element);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
if (element.children) {
|
|
1142
|
+
for (const child of element.children) {
|
|
1143
|
+
const childElement = child;
|
|
1144
|
+
const childIsRoot = isRoot && childElement.type === "tag" && (childElement.tagName.toLowerCase() === "html" || childElement.tagName.toLowerCase() === "head" || childElement.tagName.toLowerCase() === "body");
|
|
1145
|
+
search(child, childIsRoot);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
};
|
|
1150
|
+
if (parsed.head) {
|
|
1151
|
+
search(parsed.head, true);
|
|
1152
|
+
} else {
|
|
1153
|
+
if (parsed.document.children) {
|
|
1154
|
+
for (const child of parsed.document.children) {
|
|
1155
|
+
const childElement = child;
|
|
1156
|
+
const childIsRoot = childElement.type === "tag" && (childElement.tagName.toLowerCase() === "html" || childElement.tagName.toLowerCase() === "head" || childElement.tagName.toLowerCase() === "body");
|
|
1157
|
+
search(child, childIsRoot);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
return results;
|
|
1162
|
+
}
|
|
1163
|
+
function elementExists(parsed, tagName, attribute) {
|
|
1164
|
+
return findElement(parsed, tagName, attribute) !== null;
|
|
1165
|
+
}
|
|
1166
|
+
function serializeHTML(parsed) {
|
|
1167
|
+
return parsed.originalContent;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
// src/injector/meta-injector.ts
|
|
1171
|
+
var import_fs10 = require("fs");
|
|
1172
|
+
var import_dom_serializer = require("dom-serializer");
|
|
1173
|
+
function injectMetaTags(htmlContent, options = {}) {
|
|
1174
|
+
const parsed = parseHTML(htmlContent);
|
|
1175
|
+
const result = {
|
|
1176
|
+
injected: [],
|
|
1177
|
+
skipped: [],
|
|
1178
|
+
warnings: []
|
|
1179
|
+
};
|
|
1180
|
+
let head = parsed.head;
|
|
1181
|
+
if (!head) {
|
|
1182
|
+
if (parsed.html) {
|
|
1183
|
+
const headElement = {
|
|
1184
|
+
type: "tag",
|
|
1185
|
+
name: "head",
|
|
1186
|
+
tagName: "head",
|
|
1187
|
+
attribs: {},
|
|
1188
|
+
children: [],
|
|
1189
|
+
parent: parsed.html,
|
|
1190
|
+
next: null,
|
|
1191
|
+
prev: null
|
|
1192
|
+
};
|
|
1193
|
+
if (parsed.html.children) {
|
|
1194
|
+
parsed.html.children.unshift(headElement);
|
|
1195
|
+
} else {
|
|
1196
|
+
parsed.html.children = [headElement];
|
|
1197
|
+
}
|
|
1198
|
+
head = headElement;
|
|
1199
|
+
result.warnings.push("Created <head> tag (was missing)");
|
|
1200
|
+
} else {
|
|
1201
|
+
result.warnings.push("No <html> or <head> tag found, meta tags may not be injected correctly");
|
|
1202
|
+
return { html: htmlContent, result };
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
if (options.manifestPath) {
|
|
1206
|
+
const manifestHref = options.manifestPath.startsWith("/") ? options.manifestPath : `/${options.manifestPath}`;
|
|
1207
|
+
if (!elementExists(parsed, "link", { name: "rel", value: "manifest" })) {
|
|
1208
|
+
injectLinkTag(head, "manifest", manifestHref);
|
|
1209
|
+
result.injected.push(`<link rel="manifest" href="${manifestHref}">`);
|
|
1210
|
+
} else {
|
|
1211
|
+
result.skipped.push("manifest link (already exists)");
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
if (options.themeColor) {
|
|
1215
|
+
const existingThemeColor = findElement(parsed, "meta", { name: "name", value: "theme-color" });
|
|
1216
|
+
if (existingThemeColor) {
|
|
1217
|
+
updateMetaContent(existingThemeColor, options.themeColor);
|
|
1218
|
+
result.injected.push(`<meta name="theme-color" content="${options.themeColor}"> (updated)`);
|
|
1219
|
+
} else if (!elementExists(parsed, "meta", { name: "theme-color", value: options.themeColor })) {
|
|
1220
|
+
injectMetaTag(head, "theme-color", options.themeColor);
|
|
1221
|
+
result.injected.push(`<meta name="theme-color" content="${options.themeColor}">`);
|
|
1222
|
+
} else {
|
|
1223
|
+
result.skipped.push("theme-color (already exists)");
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
if (options.appleTouchIcon) {
|
|
1227
|
+
const iconHref = options.appleTouchIcon.startsWith("/") ? options.appleTouchIcon : `/${options.appleTouchIcon}`;
|
|
1228
|
+
if (!elementExists(parsed, "link", { name: "rel", value: "apple-touch-icon" })) {
|
|
1229
|
+
injectLinkTag(head, "apple-touch-icon", iconHref);
|
|
1230
|
+
result.injected.push(`<link rel="apple-touch-icon" href="${iconHref}">`);
|
|
1231
|
+
} else {
|
|
1232
|
+
result.skipped.push("apple-touch-icon (already exists)");
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
if (options.appleMobileWebAppCapable !== void 0) {
|
|
1236
|
+
const content = options.appleMobileWebAppCapable ? "yes" : "no";
|
|
1237
|
+
if (!elementExists(parsed, "meta", { name: "apple-mobile-web-app-capable", value: content })) {
|
|
1238
|
+
injectMetaTag(head, "apple-mobile-web-app-capable", content);
|
|
1239
|
+
result.injected.push(`<meta name="apple-mobile-web-app-capable" content="${content}">`);
|
|
1240
|
+
} else {
|
|
1241
|
+
result.skipped.push("apple-mobile-web-app-capable (already exists)");
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
if (options.appleMobileWebAppStatusBarStyle) {
|
|
1245
|
+
if (!elementExists(parsed, "meta", { name: "apple-mobile-web-app-status-bar-style", value: options.appleMobileWebAppStatusBarStyle })) {
|
|
1246
|
+
injectMetaTag(head, "apple-mobile-web-app-status-bar-style", options.appleMobileWebAppStatusBarStyle);
|
|
1247
|
+
result.injected.push(`<meta name="apple-mobile-web-app-status-bar-style" content="${options.appleMobileWebAppStatusBarStyle}">`);
|
|
1248
|
+
} else {
|
|
1249
|
+
result.skipped.push("apple-mobile-web-app-status-bar-style (already exists)");
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
if (options.appleMobileWebAppTitle) {
|
|
1253
|
+
if (!elementExists(parsed, "meta", { name: "apple-mobile-web-app-title", value: options.appleMobileWebAppTitle })) {
|
|
1254
|
+
injectMetaTag(head, "apple-mobile-web-app-title", options.appleMobileWebAppTitle);
|
|
1255
|
+
result.injected.push(`<meta name="apple-mobile-web-app-title" content="${options.appleMobileWebAppTitle}">`);
|
|
1256
|
+
} else {
|
|
1257
|
+
result.skipped.push("apple-mobile-web-app-title (already exists)");
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
const modifiedHtml = (0, import_dom_serializer.render)(parsed.document, { decodeEntities: false });
|
|
1261
|
+
if (options.serviceWorkerPath) {
|
|
1262
|
+
const swPath = options.serviceWorkerPath.startsWith("/") ? options.serviceWorkerPath : `/${options.serviceWorkerPath}`;
|
|
1263
|
+
if (!htmlContent.includes("navigator.serviceWorker")) {
|
|
1264
|
+
const swScript = `
|
|
1265
|
+
<script>
|
|
1266
|
+
if ('serviceWorker' in navigator) {
|
|
1267
|
+
window.addEventListener('load', () => {
|
|
1268
|
+
navigator.serviceWorker.register('${swPath}')
|
|
1269
|
+
.then((registration) => console.log('SW registered:', registration))
|
|
1270
|
+
.catch((error) => console.error('SW registration failed:', error));
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
</script>`;
|
|
1274
|
+
const finalHtml = modifiedHtml.replace("</body>", `${swScript}
|
|
1275
|
+
</body>`);
|
|
1276
|
+
result.injected.push("Service Worker registration script");
|
|
1277
|
+
return { html: finalHtml, result };
|
|
1278
|
+
} else {
|
|
1279
|
+
result.skipped.push("Service Worker registration (already exists)");
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
return { html: modifiedHtml, result };
|
|
1283
|
+
}
|
|
1284
|
+
function injectLinkTag(head, rel, href) {
|
|
1285
|
+
const linkElement = {
|
|
1286
|
+
type: "tag",
|
|
1287
|
+
name: "link",
|
|
1288
|
+
tagName: "link",
|
|
1289
|
+
attribs: {
|
|
1290
|
+
rel,
|
|
1291
|
+
href
|
|
1292
|
+
},
|
|
1293
|
+
children: [],
|
|
1294
|
+
parent: head,
|
|
1295
|
+
next: null,
|
|
1296
|
+
prev: null
|
|
1297
|
+
};
|
|
1298
|
+
if (!head.children) {
|
|
1299
|
+
head.children = [];
|
|
1300
|
+
}
|
|
1301
|
+
head.children.push(linkElement);
|
|
1302
|
+
}
|
|
1303
|
+
function injectMetaTag(head, name, content) {
|
|
1304
|
+
const metaElement = {
|
|
1305
|
+
type: "tag",
|
|
1306
|
+
name: "meta",
|
|
1307
|
+
tagName: "meta",
|
|
1308
|
+
attribs: {
|
|
1309
|
+
name,
|
|
1310
|
+
content
|
|
1311
|
+
},
|
|
1312
|
+
children: [],
|
|
1313
|
+
parent: head,
|
|
1314
|
+
next: null,
|
|
1315
|
+
prev: null
|
|
1316
|
+
};
|
|
1317
|
+
if (!head.children) {
|
|
1318
|
+
head.children = [];
|
|
1319
|
+
}
|
|
1320
|
+
head.children.push(metaElement);
|
|
1321
|
+
}
|
|
1322
|
+
function updateMetaContent(metaElement, newContent) {
|
|
1323
|
+
if (metaElement.attribs) {
|
|
1324
|
+
metaElement.attribs.content = newContent;
|
|
1325
|
+
} else {
|
|
1326
|
+
metaElement.attribs = { content: newContent };
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
function injectMetaTagsInFile(filePath, options = {}) {
|
|
1330
|
+
const parsed = parseHTMLFile(filePath);
|
|
1331
|
+
const { html, result } = injectMetaTags(parsed.originalContent, options);
|
|
1332
|
+
(0, import_fs10.writeFileSync)(filePath, html, "utf-8");
|
|
1333
|
+
return result;
|
|
1334
|
+
}
|
|
1335
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1336
|
+
0 && (module.exports = {
|
|
1337
|
+
ManifestSchema,
|
|
1338
|
+
STANDARD_ICON_SIZES,
|
|
1339
|
+
STANDARD_SPLASH_SIZES,
|
|
1340
|
+
checkHttps,
|
|
1341
|
+
checkProjectHttps,
|
|
1342
|
+
detectArchitecture,
|
|
1343
|
+
detectAssets,
|
|
1344
|
+
detectFramework,
|
|
1345
|
+
detectProjectUrl,
|
|
1346
|
+
elementExists,
|
|
1347
|
+
findAllElements,
|
|
1348
|
+
findElement,
|
|
1349
|
+
generateAndWriteManifest,
|
|
1350
|
+
generateAndWriteServiceWorker,
|
|
1351
|
+
generateAppleTouchIcon,
|
|
1352
|
+
generateFavicon,
|
|
1353
|
+
generateIcons,
|
|
1354
|
+
generateIconsOnly,
|
|
1355
|
+
generateManifest,
|
|
1356
|
+
generateReport,
|
|
1357
|
+
generateServiceWorker,
|
|
1358
|
+
generateSimpleServiceWorker,
|
|
1359
|
+
generateSplashScreensOnly,
|
|
1360
|
+
injectMetaTags,
|
|
1361
|
+
injectMetaTagsInFile,
|
|
1362
|
+
parseHTML,
|
|
1363
|
+
parseHTMLFile,
|
|
1364
|
+
scanProject,
|
|
1365
|
+
serializeHTML,
|
|
1366
|
+
validateProjectPath,
|
|
1367
|
+
writeManifest
|
|
1368
|
+
});
|