@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/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
+ });