@julien-lin/universal-pwa-cli 1.3.2 → 1.3.4

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.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
 
6
6
  // src/index.ts
7
7
  import { Command } from "commander";
8
- import chalk6 from "chalk";
8
+ import chalk7 from "chalk";
9
9
 
10
10
  // src/commands/init.ts
11
11
  import { scanProject, optimizeProject } from "@julien-lin/universal-pwa-core";
@@ -510,7 +510,8 @@ async function initCommand(options = {}) {
510
510
  }
511
511
  let finalOutputDir;
512
512
  if (outputDir) {
513
- finalOutputDir = resolve(outputDir);
513
+ finalOutputDir = outputDir.startsWith("/") || process.platform === "win32" && /^[A-Z]:/.test(outputDir) ? resolve(outputDir) : join2(result.projectPath, outputDir);
514
+ console.log(chalk2.gray(` Using output directory: ${finalOutputDir}`));
514
515
  } else {
515
516
  const distDir = join2(result.projectPath, "dist");
516
517
  const publicDir = join2(result.projectPath, "public");
@@ -748,9 +749,9 @@ async function initCommand(options = {}) {
748
749
  if (!skipInjection) {
749
750
  console.log(chalk2.blue("\u{1F489} Injecting meta-tags..."));
750
751
  try {
751
- const htmlFiles = await glob("**/*.html", {
752
+ const htmlFiles = await glob("**/*.{html,twig,html.twig,blade.php}", {
752
753
  cwd: result.projectPath,
753
- ignore: ["**/node_modules/**", "**/.next/**", "**/.nuxt/**"],
754
+ ignore: ["**/node_modules/**", "**/.next/**", "**/.nuxt/**", "**/vendor/**"],
754
755
  absolute: true
755
756
  });
756
757
  htmlFiles.sort((a, b) => {
@@ -766,7 +767,15 @@ async function initCommand(options = {}) {
766
767
  });
767
768
  const htmlFilesToProcess = maxHtmlFiles && maxHtmlFiles > 0 ? htmlFiles.slice(0, maxHtmlFiles) : htmlFiles;
768
769
  if (htmlFiles.length > 0) {
769
- console.log(chalk2.gray(` Found ${htmlFiles.length} HTML file(s)${maxHtmlFiles && maxHtmlFiles > 0 ? ` (processing ${htmlFilesToProcess.length})` : ""}`));
770
+ const htmlCount = htmlFiles.filter((f) => f.endsWith(".html") && !f.endsWith(".html.twig")).length;
771
+ const twigCount = htmlFiles.filter((f) => f.endsWith(".twig") || f.endsWith(".html.twig")).length;
772
+ const bladeCount = htmlFiles.filter((f) => f.endsWith(".blade.php")).length;
773
+ const fileTypes = [];
774
+ if (htmlCount > 0) fileTypes.push(`${htmlCount} HTML`);
775
+ if (twigCount > 0) fileTypes.push(`${twigCount} Twig`);
776
+ if (bladeCount > 0) fileTypes.push(`${bladeCount} Blade`);
777
+ const typeSummary = fileTypes.length > 0 ? ` (${fileTypes.join(", ")})` : "";
778
+ console.log(chalk2.gray(` Found ${htmlFiles.length} template file(s)${typeSummary}${maxHtmlFiles && maxHtmlFiles > 0 ? ` (processing ${htmlFilesToProcess.length})` : ""}`));
770
779
  }
771
780
  for (const htmlFile of htmlFilesToProcess) {
772
781
  const htmlRelativePath = relative(result.projectPath, htmlFile);
@@ -834,7 +843,8 @@ async function initCommand(options = {}) {
834
843
  }
835
844
  }
836
845
  result.htmlFilesInjected = injectedCount;
837
- console.log(chalk2.green(`\u2713 Injected meta-tags in ${injectedCount} HTML file(s)`));
846
+ const fileTypeLabel = htmlFilesToProcess.some((f) => f.endsWith(".twig") || f.endsWith(".html.twig") || f.endsWith(".blade.php")) ? "template file(s)" : "HTML file(s)";
847
+ console.log(chalk2.green(`\u2713 Injected meta-tags in ${injectedCount} ${fileTypeLabel}`));
838
848
  } catch (error) {
839
849
  const errorMessage = error instanceof Error ? error.message : String(error);
840
850
  const errorCode = detectErrorCode(error);
@@ -1150,24 +1160,253 @@ async function verifyCommand(options = {}) {
1150
1160
  }
1151
1161
  }
1152
1162
 
1163
+ // src/commands/remove.ts
1164
+ import { scanProject as scanProject2, parseHTML } from "@julien-lin/universal-pwa-core";
1165
+ import chalk5 from "chalk";
1166
+ import { existsSync as existsSync5, readFileSync as readFileSync3, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
1167
+ import { glob as glob3 } from "glob";
1168
+ import { join as join5, resolve as resolve4, relative as relative2 } from "path";
1169
+ var PWA_FILES_TO_REMOVE = [
1170
+ "manifest.json",
1171
+ "sw.js",
1172
+ "apple-touch-icon.png",
1173
+ "icon-72x72.png",
1174
+ "icon-96x96.png",
1175
+ "icon-128x128.png",
1176
+ "icon-144x144.png",
1177
+ "icon-152x152.png",
1178
+ "icon-192x192.png",
1179
+ "icon-384x384.png",
1180
+ "icon-512x512.png"
1181
+ ];
1182
+ function isPWAScript(content) {
1183
+ const lowerContent = content.toLowerCase();
1184
+ return lowerContent.includes("serviceworker") || lowerContent.includes("navigator.serviceworker") || lowerContent.includes("register") && lowerContent.includes("sw") || lowerContent.includes("sw.js") || lowerContent.includes("beforeinstallprompt") || lowerContent.includes("window.installpwa") || lowerContent.includes("ispwainstalled") || lowerContent.includes("ispwainstallable") || lowerContent.includes("deferredprompt");
1185
+ }
1186
+ async function removeMetaTags(htmlContent) {
1187
+ const parsed = parseHTML(htmlContent);
1188
+ const removed = [];
1189
+ if (!parsed.head) {
1190
+ return { html: htmlContent, removed: [] };
1191
+ }
1192
+ const head = parsed.head;
1193
+ if (!head.children) {
1194
+ head.children = [];
1195
+ }
1196
+ const tagsToRemove = [
1197
+ { type: "link", attr: "rel", value: "manifest", name: "manifest link" },
1198
+ { type: "meta", attr: "name", value: "theme-color", name: "theme-color meta tag" },
1199
+ { type: "link", attr: "rel", value: "apple-touch-icon", name: "apple-touch-icon link" },
1200
+ { type: "meta", attr: "name", value: "mobile-web-app-capable", name: "mobile-web-app-capable meta tag" }
1201
+ ];
1202
+ for (const tag of tagsToRemove) {
1203
+ const index = head.children.findIndex((child) => {
1204
+ return child.type === "tag" && child.tagName === tag.type && child.attribs?.[tag.attr] === tag.value;
1205
+ });
1206
+ if (index !== -1) {
1207
+ head.children.splice(index, 1);
1208
+ removed.push(tag.name);
1209
+ }
1210
+ }
1211
+ const findAndRemoveScripts = (node) => {
1212
+ if (!node?.children) return;
1213
+ for (let i = node.children.length - 1; i >= 0; i--) {
1214
+ const child = node.children[i];
1215
+ if (child.type === "tag" && child.tagName === "script") {
1216
+ const content = child.children?.[0]?.data || "";
1217
+ if (isPWAScript(content)) {
1218
+ node.children.splice(i, 1);
1219
+ removed.push("service worker registration script");
1220
+ }
1221
+ } else if (child.children) {
1222
+ findAndRemoveScripts(child);
1223
+ }
1224
+ }
1225
+ };
1226
+ if (parsed.document) {
1227
+ findAndRemoveScripts(parsed.document);
1228
+ }
1229
+ const { render } = await import("./esm-BSZHJOVR.js");
1230
+ const html = render(parsed.document);
1231
+ return { html, removed };
1232
+ }
1233
+ async function removeCommand(options = {}) {
1234
+ const {
1235
+ projectPath = process.cwd(),
1236
+ outputDir,
1237
+ skipHtmlRestore = false,
1238
+ skipFiles = false
1239
+ } = options;
1240
+ const result = {
1241
+ success: false,
1242
+ projectPath: resolve4(projectPath),
1243
+ outputDir: "",
1244
+ filesRemoved: [],
1245
+ htmlFilesRestored: 0,
1246
+ warnings: [],
1247
+ errors: []
1248
+ };
1249
+ let transaction = null;
1250
+ try {
1251
+ if (!existsSync5(result.projectPath)) {
1252
+ const errorCode = "E1001" /* PROJECT_PATH_NOT_FOUND */;
1253
+ const errorMessage = formatError(errorCode, result.projectPath);
1254
+ result.errors.push(errorMessage);
1255
+ console.log(chalk5.red(`\u2717 ${errorMessage}`));
1256
+ return result;
1257
+ }
1258
+ console.log(chalk5.blue("\u{1F50D} Scanning project for PWA files..."));
1259
+ const scanResult = await scanProject2({
1260
+ projectPath: result.projectPath,
1261
+ includeAssets: false,
1262
+ includeArchitecture: false
1263
+ });
1264
+ let finalOutputDir;
1265
+ if (outputDir) {
1266
+ finalOutputDir = outputDir.startsWith("/") || process.platform === "win32" && /^[A-Z]:/.test(outputDir) ? resolve4(outputDir) : join5(result.projectPath, outputDir);
1267
+ } else {
1268
+ const envDetection = detectEnvironment(result.projectPath, scanResult.framework.framework);
1269
+ const distDir = join5(result.projectPath, "dist");
1270
+ const publicDir = join5(result.projectPath, "public");
1271
+ if (envDetection.suggestedOutputDir === "dist" && existsSync5(distDir)) {
1272
+ finalOutputDir = distDir;
1273
+ } else if (envDetection.suggestedOutputDir === "build" && existsSync5(join5(result.projectPath, "build"))) {
1274
+ finalOutputDir = join5(result.projectPath, "build");
1275
+ } else if (existsSync5(publicDir)) {
1276
+ finalOutputDir = publicDir;
1277
+ } else if (existsSync5(distDir)) {
1278
+ finalOutputDir = distDir;
1279
+ } else {
1280
+ finalOutputDir = publicDir;
1281
+ }
1282
+ }
1283
+ result.outputDir = finalOutputDir;
1284
+ console.log(chalk5.gray(` Output directory: ${finalOutputDir}`));
1285
+ transaction = new Transaction({
1286
+ projectPath: result.projectPath,
1287
+ outputDir: relative2(result.projectPath, finalOutputDir) || void 0,
1288
+ verbose: false
1289
+ });
1290
+ const pwaFiles = [...PWA_FILES_TO_REMOVE];
1291
+ if (existsSync5(finalOutputDir)) {
1292
+ const workboxFiles = await glob3("workbox-*.js", {
1293
+ cwd: finalOutputDir,
1294
+ absolute: true
1295
+ });
1296
+ pwaFiles.push(...workboxFiles.map((f) => relative2(finalOutputDir, f)));
1297
+ }
1298
+ if (!skipFiles) {
1299
+ console.log(chalk5.blue("\u{1F5D1}\uFE0F Removing PWA files..."));
1300
+ for (const file of pwaFiles) {
1301
+ const filePath = join5(finalOutputDir, file);
1302
+ if (existsSync5(filePath)) {
1303
+ try {
1304
+ const fileRelative = relative2(result.projectPath, filePath);
1305
+ if (fileRelative && !fileRelative.startsWith("..")) {
1306
+ transaction.backupFile(fileRelative);
1307
+ }
1308
+ rmSync2(filePath, { force: true });
1309
+ result.filesRemoved.push(file);
1310
+ console.log(chalk5.green(` \u2713 Removed ${file}`));
1311
+ } catch (error) {
1312
+ const errorMessage = error instanceof Error ? error.message : String(error);
1313
+ result.warnings.push(`Failed to remove ${file}: ${errorMessage}`);
1314
+ console.log(chalk5.yellow(` \u26A0 Failed to remove ${file}: ${errorMessage}`));
1315
+ }
1316
+ }
1317
+ }
1318
+ if (result.filesRemoved.length === 0) {
1319
+ console.log(chalk5.gray(" No PWA files found to remove"));
1320
+ } else {
1321
+ console.log(chalk5.green(`\u2713 Removed ${result.filesRemoved.length} file(s)`));
1322
+ }
1323
+ }
1324
+ if (!skipHtmlRestore) {
1325
+ console.log(chalk5.blue("\u{1F489} Restoring HTML files..."));
1326
+ const htmlFiles = await glob3("**/*.html", {
1327
+ cwd: result.projectPath,
1328
+ ignore: ["**/node_modules/**", "**/.next/**", "**/.nuxt/**"],
1329
+ absolute: true
1330
+ });
1331
+ if (htmlFiles.length > 0) {
1332
+ console.log(chalk5.gray(` Found ${htmlFiles.length} HTML file(s)`));
1333
+ for (const htmlFile of htmlFiles) {
1334
+ try {
1335
+ const htmlRelative = relative2(result.projectPath, htmlFile);
1336
+ if (htmlRelative && !htmlRelative.startsWith("..")) {
1337
+ transaction.backupFile(htmlRelative);
1338
+ }
1339
+ const htmlContent = readFileSync3(htmlFile, "utf-8");
1340
+ const { html: restoredHtml, removed } = await removeMetaTags(htmlContent);
1341
+ if (removed.length > 0) {
1342
+ writeFileSync2(htmlFile, restoredHtml, "utf-8");
1343
+ result.htmlFilesRestored++;
1344
+ console.log(chalk5.green(` \u2713 Restored ${htmlRelative} (removed ${removed.length} PWA element(s))`));
1345
+ }
1346
+ } catch (error) {
1347
+ const errorMessage = error instanceof Error ? error.message : String(error);
1348
+ result.warnings.push(`Failed to restore ${htmlFile}: ${errorMessage}`);
1349
+ console.log(chalk5.yellow(` \u26A0 Failed to restore ${htmlFile}: ${errorMessage}`));
1350
+ }
1351
+ }
1352
+ if (result.htmlFilesRestored > 0) {
1353
+ console.log(chalk5.green(`\u2713 Restored ${result.htmlFilesRestored} HTML file(s)`));
1354
+ } else {
1355
+ console.log(chalk5.gray(" No PWA meta-tags found in HTML files"));
1356
+ }
1357
+ } else {
1358
+ console.log(chalk5.gray(" No HTML files found"));
1359
+ }
1360
+ }
1361
+ result.success = result.errors.length === 0;
1362
+ if (result.success) {
1363
+ if (transaction) {
1364
+ transaction.commit();
1365
+ }
1366
+ console.log(chalk5.green("\n\u2705 PWA removal completed successfully!"));
1367
+ console.log(chalk5.gray(` Files removed: ${result.filesRemoved.length}`));
1368
+ console.log(chalk5.gray(` HTML files restored: ${result.htmlFilesRestored}`));
1369
+ } else {
1370
+ if (transaction) {
1371
+ console.log(chalk5.yellow("\n\u{1F504} Rolling back changes due to errors..."));
1372
+ transaction.rollback();
1373
+ }
1374
+ console.log(chalk5.red(`
1375
+ \u274C PWA removal completed with ${result.errors.length} error(s)`));
1376
+ }
1377
+ return result;
1378
+ } catch (error) {
1379
+ const errorMessage = error instanceof Error ? error.message : String(error);
1380
+ const errorCode = "E9001" /* UNEXPECTED_ERROR */;
1381
+ const formattedError = formatError(errorCode, errorMessage);
1382
+ result.errors.push(formattedError);
1383
+ console.log(chalk5.red(`\u2717 ${formattedError}`));
1384
+ if (transaction) {
1385
+ console.log(chalk5.yellow("\n\u{1F504} Rolling back changes due to unexpected error..."));
1386
+ transaction.rollback();
1387
+ }
1388
+ return result;
1389
+ }
1390
+ }
1391
+
1153
1392
  // src/index.ts
1154
- import { scanProject as scanProject2 } from "@julien-lin/universal-pwa-core";
1393
+ import { scanProject as scanProject3 } from "@julien-lin/universal-pwa-core";
1155
1394
 
1156
1395
  // src/prompts.ts
1157
1396
  import inquirer from "inquirer";
1158
- import { existsSync as existsSync6 } from "fs";
1159
- import { join as join6, extname } from "path";
1160
- import chalk5 from "chalk";
1397
+ import { existsSync as existsSync7 } from "fs";
1398
+ import { join as join7, extname } from "path";
1399
+ import chalk6 from "chalk";
1161
1400
 
1162
1401
  // src/utils/suggestions.ts
1163
- import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
1164
- import { join as join5 } from "path";
1402
+ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
1403
+ import { join as join6 } from "path";
1165
1404
  import { globSync } from "glob";
1166
1405
  function suggestAppName(projectPath, _framework) {
1167
- const packageJsonPath = join5(projectPath, "package.json");
1168
- if (existsSync5(packageJsonPath)) {
1406
+ const packageJsonPath = join6(projectPath, "package.json");
1407
+ if (existsSync6(packageJsonPath)) {
1169
1408
  try {
1170
- const packageContent = JSON.parse(readFileSync3(packageJsonPath, "utf-8"));
1409
+ const packageContent = JSON.parse(readFileSync4(packageJsonPath, "utf-8"));
1171
1410
  const name = packageContent.displayName || packageContent.productName || packageContent.name;
1172
1411
  if (name && typeof name === "string" && name.trim().length > 0) {
1173
1412
  const cleanName = name.trim();
@@ -1183,10 +1422,10 @@ function suggestAppName(projectPath, _framework) {
1183
1422
  } catch {
1184
1423
  }
1185
1424
  }
1186
- const composerJsonPath = join5(projectPath, "composer.json");
1187
- if (existsSync5(composerJsonPath)) {
1425
+ const composerJsonPath = join6(projectPath, "composer.json");
1426
+ if (existsSync6(composerJsonPath)) {
1188
1427
  try {
1189
- const composerContent = JSON.parse(readFileSync3(composerJsonPath, "utf-8"));
1428
+ const composerContent = JSON.parse(readFileSync4(composerJsonPath, "utf-8"));
1190
1429
  if (composerContent.name && typeof composerContent.name === "string") {
1191
1430
  const parts = composerContent.name.split("/");
1192
1431
  const packageName = parts[parts.length - 1] || composerContent.name;
@@ -1234,8 +1473,8 @@ function suggestIconPath(projectPath) {
1234
1473
  "static/icon.png"
1235
1474
  ];
1236
1475
  for (const iconPath of commonIconPaths) {
1237
- const fullPath = join5(projectPath, iconPath);
1238
- if (existsSync5(fullPath)) {
1476
+ const fullPath = join6(projectPath, iconPath);
1477
+ if (existsSync6(fullPath)) {
1239
1478
  suggestions.push({
1240
1479
  path: iconPath,
1241
1480
  confidence: "high",
@@ -1302,18 +1541,18 @@ function suggestColors(_projectPath, framework, _iconPath) {
1302
1541
  };
1303
1542
  }
1304
1543
  function suggestConfiguration(projectPath, framework, _architecture) {
1305
- const distDir = join5(projectPath, "dist");
1306
- const buildDir = join5(projectPath, "build");
1307
- const publicDir = join5(projectPath, "public");
1544
+ const distDir = join6(projectPath, "dist");
1545
+ const buildDir = join6(projectPath, "build");
1546
+ const publicDir = join6(projectPath, "public");
1308
1547
  let outputDir = "public";
1309
1548
  let reason = "Default output directory";
1310
- if (existsSync5(distDir)) {
1549
+ if (existsSync6(distDir)) {
1311
1550
  outputDir = "dist";
1312
1551
  reason = "dist/ directory detected (production build)";
1313
- } else if (existsSync5(buildDir)) {
1552
+ } else if (existsSync6(buildDir)) {
1314
1553
  outputDir = "build";
1315
1554
  reason = "build/ directory detected (production build)";
1316
- } else if (existsSync5(publicDir)) {
1555
+ } else if (existsSync6(publicDir)) {
1317
1556
  outputDir = "public";
1318
1557
  reason = "public/ directory detected";
1319
1558
  } else if (framework === "wordpress" || framework === "drupal" || framework === "joomla") {
@@ -1339,21 +1578,81 @@ function generateSuggestions(projectPath, framework, architecture, iconPath) {
1339
1578
  }
1340
1579
 
1341
1580
  // src/prompts.ts
1581
+ function validateName(input) {
1582
+ if (!input || input.trim().length === 0) {
1583
+ return "Le nom de l'application est requis";
1584
+ }
1585
+ if (input.length > 50) {
1586
+ return "Le nom doit faire moins de 50 caract\xE8res";
1587
+ }
1588
+ return true;
1589
+ }
1590
+ function validateShortName(input) {
1591
+ if (!input || input.trim().length === 0) {
1592
+ return "Le nom court est requis";
1593
+ }
1594
+ if (input.length > 12) {
1595
+ return "Le nom court doit faire maximum 12 caract\xE8res";
1596
+ }
1597
+ return true;
1598
+ }
1599
+ function filterShortName(input) {
1600
+ return input.trim().substring(0, 12);
1601
+ }
1602
+ function validateIconSource(input, projectPath) {
1603
+ if (!input || input.trim().length === 0) {
1604
+ return true;
1605
+ }
1606
+ const fullPath = existsSync7(input) ? input : join7(projectPath, input);
1607
+ if (!existsSync7(fullPath)) {
1608
+ const suggestions = [
1609
+ "public/logo.png",
1610
+ "src/assets/icon.png",
1611
+ "assets/logo.png",
1612
+ "logo.png"
1613
+ ].join(", ");
1614
+ return `Le fichier n'existe pas: ${input}
1615
+ Suggestions: ${suggestions}`;
1616
+ }
1617
+ const ext = extname(fullPath).toLowerCase();
1618
+ const supportedFormats = [".png", ".jpg", ".jpeg", ".svg", ".webp"];
1619
+ if (!supportedFormats.includes(ext)) {
1620
+ return `Format non support\xE9: ${ext}. Utilisez PNG, JPG, SVG ou WebP`;
1621
+ }
1622
+ return true;
1623
+ }
1624
+ function validateHexColor(input, fieldName) {
1625
+ if (!input || input.trim().length === 0) {
1626
+ return true;
1627
+ }
1628
+ const trimmed = input.trim();
1629
+ if (!/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(trimmed)) {
1630
+ return `Format hex invalide (ex: ${fieldName === "themeColor" ? "#ffffff ou #fff" : "#000000 ou #000"})`;
1631
+ }
1632
+ return true;
1633
+ }
1634
+ function filterHexColor(input) {
1635
+ const trimmed = input.trim();
1636
+ if (/^#[A-Fa-f0-9]{3}$/.test(trimmed)) {
1637
+ return `#${trimmed[1]}${trimmed[1]}${trimmed[2]}${trimmed[2]}${trimmed[3]}${trimmed[3]}`;
1638
+ }
1639
+ return trimmed;
1640
+ }
1342
1641
  async function promptInitOptions(projectPath, framework, architecture = null) {
1343
1642
  const suggestions = generateSuggestions(projectPath, framework, architecture);
1344
1643
  const defaultName = suggestions.name.name;
1345
1644
  const defaultShortName = suggestions.name.shortName;
1346
1645
  const defaultIconSource = suggestions.icons.length > 0 ? suggestions.icons[0].path : void 0;
1347
1646
  const envDetection = detectEnvironment(projectPath, framework);
1348
- console.log(chalk5.blue("\n\u{1F4CB} Configuration PWA\n"));
1647
+ console.log(chalk6.blue("\n\u{1F4CB} Configuration PWA\n"));
1349
1648
  if (suggestions.name.confidence === "high") {
1350
- console.log(chalk5.gray(`\u{1F4A1} Suggestion: Nom "${suggestions.name.name}" (${suggestions.name.source})`));
1649
+ console.log(chalk6.gray(`\u{1F4A1} Suggestion: Nom "${suggestions.name.name}" (${suggestions.name.source})`));
1351
1650
  }
1352
1651
  if (suggestions.icons.length > 0) {
1353
- console.log(chalk5.gray(`\u{1F4A1} Suggestion: ${suggestions.icons.length} ic\xF4ne(s) trouv\xE9e(s)`));
1652
+ console.log(chalk6.gray(`\u{1F4A1} Suggestion: ${suggestions.icons.length} ic\xF4ne(s) trouv\xE9e(s)`));
1354
1653
  }
1355
1654
  if (suggestions.colors.confidence === "high") {
1356
- console.log(chalk5.gray(`\u{1F4A1} Suggestion: Couleurs bas\xE9es sur ${framework}`));
1655
+ console.log(chalk6.gray(`\u{1F4A1} Suggestion: Couleurs bas\xE9es sur ${framework}`));
1357
1656
  }
1358
1657
  const environmentAnswer = await inquirer.prompt([
1359
1658
  {
@@ -1376,7 +1675,7 @@ async function promptInitOptions(projectPath, framework, architecture = null) {
1376
1675
  }
1377
1676
  ]);
1378
1677
  if (envDetection.indicators.length > 0) {
1379
- console.log(chalk5.gray(` ${envDetection.indicators.join(", ")}`));
1678
+ console.log(chalk6.gray(` ${envDetection.indicators.join(", ")}`));
1380
1679
  }
1381
1680
  const configAnswers = await inquirer.prompt([
1382
1681
  {
@@ -1384,15 +1683,7 @@ async function promptInitOptions(projectPath, framework, architecture = null) {
1384
1683
  name: "name",
1385
1684
  message: "Nom de l'application:",
1386
1685
  default: defaultName,
1387
- validate: (input) => {
1388
- if (!input || input.trim().length === 0) {
1389
- return "Le nom de l'application est requis";
1390
- }
1391
- if (input.length > 50) {
1392
- return "Le nom doit faire moins de 50 caract\xE8res";
1393
- }
1394
- return true;
1395
- }
1686
+ validate: validateName
1396
1687
  },
1397
1688
  {
1398
1689
  type: "input",
@@ -1401,44 +1692,15 @@ async function promptInitOptions(projectPath, framework, architecture = null) {
1401
1692
  default: (answers) => {
1402
1693
  return answers.name ? answers.name.substring(0, 12) : defaultShortName;
1403
1694
  },
1404
- validate: (input) => {
1405
- if (!input || input.trim().length === 0) {
1406
- return "Le nom court est requis";
1407
- }
1408
- if (input.length > 12) {
1409
- return "Le nom court doit faire maximum 12 caract\xE8res";
1410
- }
1411
- return true;
1412
- },
1413
- filter: (input) => input.trim().substring(0, 12)
1695
+ validate: validateShortName,
1696
+ filter: filterShortName
1414
1697
  },
1415
1698
  {
1416
1699
  type: "input",
1417
1700
  name: "iconSource",
1418
1701
  message: "Chemin vers l'image source pour les ic\xF4nes:",
1419
1702
  default: defaultIconSource,
1420
- validate: (input) => {
1421
- if (!input || input.trim().length === 0) {
1422
- return true;
1423
- }
1424
- const fullPath = existsSync6(input) ? input : join6(projectPath, input);
1425
- if (!existsSync6(fullPath)) {
1426
- const suggestions2 = [
1427
- "public/logo.png",
1428
- "src/assets/icon.png",
1429
- "assets/logo.png",
1430
- "logo.png"
1431
- ].join(", ");
1432
- return `Le fichier n'existe pas: ${input}
1433
- Suggestions: ${suggestions2}`;
1434
- }
1435
- const ext = extname(fullPath).toLowerCase();
1436
- const supportedFormats = [".png", ".jpg", ".jpeg", ".svg", ".webp"];
1437
- if (!supportedFormats.includes(ext)) {
1438
- return `Format non support\xE9: ${ext}. Utilisez PNG, JPG, SVG ou WebP`;
1439
- }
1440
- return true;
1441
- }
1703
+ validate: (input) => validateIconSource(input, projectPath)
1442
1704
  },
1443
1705
  {
1444
1706
  type: "confirm",
@@ -1454,46 +1716,16 @@ Suggestions: ${suggestions2}`;
1454
1716
  name: "themeColor",
1455
1717
  message: "Couleur du th\xE8me (hex, ex: #ffffff):",
1456
1718
  default: suggestions.colors.themeColor,
1457
- validate: (input) => {
1458
- if (!input || input.trim().length === 0) {
1459
- return true;
1460
- }
1461
- const trimmed = input.trim();
1462
- if (!/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(trimmed)) {
1463
- return "Format hex invalide (ex: #ffffff ou #fff)";
1464
- }
1465
- return true;
1466
- },
1467
- filter: (input) => {
1468
- const trimmed = input.trim();
1469
- if (/^#[A-Fa-f0-9]{3}$/.test(trimmed)) {
1470
- return `#${trimmed[1]}${trimmed[1]}${trimmed[2]}${trimmed[2]}${trimmed[3]}${trimmed[3]}`;
1471
- }
1472
- return trimmed;
1473
- }
1719
+ validate: (input) => validateHexColor(input, "themeColor"),
1720
+ filter: filterHexColor
1474
1721
  },
1475
1722
  {
1476
1723
  type: "input",
1477
1724
  name: "backgroundColor",
1478
1725
  message: "Couleur de fond (hex, ex: #000000):",
1479
1726
  default: suggestions.colors.backgroundColor,
1480
- validate: (input) => {
1481
- if (!input || input.trim().length === 0) {
1482
- return true;
1483
- }
1484
- const trimmed = input.trim();
1485
- if (!/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(trimmed)) {
1486
- return "Format hex invalide (ex: #000000 ou #000)";
1487
- }
1488
- return true;
1489
- },
1490
- filter: (input) => {
1491
- const trimmed = input.trim();
1492
- if (/^#[A-Fa-f0-9]{3}$/.test(trimmed)) {
1493
- return `#${trimmed[1]}${trimmed[1]}${trimmed[2]}${trimmed[2]}${trimmed[3]}${trimmed[3]}`;
1494
- }
1495
- return trimmed;
1496
- }
1727
+ validate: (input) => validateHexColor(input, "backgroundColor"),
1728
+ filter: filterHexColor
1497
1729
  }
1498
1730
  ]);
1499
1731
  configAnswers.skipIcons = !configAnswers.skipIcons;
@@ -1515,7 +1747,7 @@ Suggestions: ${suggestions2}`;
1515
1747
  // package.json
1516
1748
  var package_default = {
1517
1749
  name: "@julien-lin/universal-pwa-cli",
1518
- version: "1.3.2",
1750
+ version: "1.3.4",
1519
1751
  description: "CLI to transform any web project into a PWA",
1520
1752
  keywords: [
1521
1753
  "pwa",
@@ -1604,14 +1836,14 @@ program.command("init").description("Initialize PWA in your project").option("-p
1604
1836
  const hasOptions = options.name || options.shortName || options.iconSource || options.themeColor || options.backgroundColor || options.skipIcons !== void 0;
1605
1837
  let finalOptions = { ...options };
1606
1838
  if (!hasOptions) {
1607
- console.log(chalk6.blue("\u{1F50D} Scanning project..."));
1608
- const scanResult = await scanProject2({
1839
+ console.log(chalk7.blue("\u{1F50D} Scanning project..."));
1840
+ const scanResult = await scanProject3({
1609
1841
  projectPath,
1610
1842
  includeAssets: false,
1611
1843
  includeArchitecture: false
1612
1844
  });
1613
- console.log(chalk6.green(`\u2713 Framework detected: ${scanResult.framework.framework ?? "Unknown"}`));
1614
- console.log(chalk6.green(`\u2713 Architecture: ${scanResult.architecture.architecture}`));
1845
+ console.log(chalk7.green(`\u2713 Framework detected: ${scanResult.framework.framework ?? "Unknown"}`));
1846
+ console.log(chalk7.green(`\u2713 Architecture: ${scanResult.architecture.architecture}`));
1615
1847
  const promptAnswers = await promptInitOptions(
1616
1848
  projectPath,
1617
1849
  scanResult.framework.framework,
@@ -1626,10 +1858,10 @@ program.command("init").description("Initialize PWA in your project").option("-p
1626
1858
  finalOptions.outputDir = "build";
1627
1859
  } else {
1628
1860
  finalOptions.outputDir = "dist";
1629
- console.log(chalk6.yellow("\u26A0 dist/ directory not found. Run build first:"));
1630
- console.log(chalk6.gray(" npm run build"));
1631
- console.log(chalk6.gray(" or"));
1632
- console.log(chalk6.gray(" pnpm build"));
1861
+ console.log(chalk7.yellow("\u26A0 dist/ directory not found. Run build first:"));
1862
+ console.log(chalk7.gray(" npm run build"));
1863
+ console.log(chalk7.gray(" or"));
1864
+ console.log(chalk7.gray(" pnpm build"));
1633
1865
  }
1634
1866
  } else {
1635
1867
  finalOptions.outputDir = "public";
@@ -1661,7 +1893,7 @@ program.command("init").description("Initialize PWA in your project").option("-p
1661
1893
  });
1662
1894
  process.exit(result.success ? 0 : 1);
1663
1895
  } catch (error) {
1664
- console.error(chalk6.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1896
+ console.error(chalk7.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1665
1897
  process.exit(1);
1666
1898
  }
1667
1899
  });
@@ -1674,31 +1906,31 @@ program.command("preview").description("Preview PWA setup").option("-p, --projec
1674
1906
  });
1675
1907
  process.exit(result.success ? 0 : 1);
1676
1908
  } catch (error) {
1677
- console.error(chalk6.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1909
+ console.error(chalk7.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1678
1910
  process.exit(1);
1679
1911
  }
1680
1912
  });
1681
1913
  program.command("scan").description("Scan project and detect framework/architecture").option("-p, --project-path <path>", "Project path", process.cwd()).action(async (options) => {
1682
1914
  try {
1683
- console.log(chalk6.blue("\u{1F50D} Scanning project..."));
1684
- const result = await scanProject2({
1915
+ console.log(chalk7.blue("\u{1F50D} Scanning project..."));
1916
+ const result = await scanProject3({
1685
1917
  projectPath: options.projectPath ?? process.cwd(),
1686
1918
  includeAssets: true,
1687
1919
  includeArchitecture: true
1688
1920
  });
1689
- console.log(chalk6.green(`
1921
+ console.log(chalk7.green(`
1690
1922
  \u2713 Framework: ${result.framework.framework ?? "Unknown"}`));
1691
- console.log(chalk6.green(`\u2713 Architecture: ${result.architecture.architecture}`));
1692
- console.log(chalk6.green(`\u2713 Build Tool: ${result.architecture.buildTool ?? "Unknown"}`));
1693
- console.log(chalk6.gray(`
1923
+ console.log(chalk7.green(`\u2713 Architecture: ${result.architecture.architecture}`));
1924
+ console.log(chalk7.green(`\u2713 Build Tool: ${result.architecture.buildTool ?? "Unknown"}`));
1925
+ console.log(chalk7.gray(`
1694
1926
  Assets found:`));
1695
- console.log(chalk6.gray(` - JavaScript: ${result.assets.javascript.length} files`));
1696
- console.log(chalk6.gray(` - CSS: ${result.assets.css.length} files`));
1697
- console.log(chalk6.gray(` - Images: ${result.assets.images.length} files`));
1698
- console.log(chalk6.gray(` - Fonts: ${result.assets.fonts.length} files`));
1927
+ console.log(chalk7.gray(` - JavaScript: ${result.assets.javascript.length} files`));
1928
+ console.log(chalk7.gray(` - CSS: ${result.assets.css.length} files`));
1929
+ console.log(chalk7.gray(` - Images: ${result.assets.images.length} files`));
1930
+ console.log(chalk7.gray(` - Fonts: ${result.assets.fonts.length} files`));
1699
1931
  process.exit(0);
1700
1932
  } catch (error) {
1701
- console.error(chalk6.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1933
+ console.error(chalk7.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1702
1934
  process.exit(1);
1703
1935
  }
1704
1936
  });
@@ -1711,7 +1943,22 @@ program.command("verify").description("Verify PWA setup and check for missing fi
1711
1943
  });
1712
1944
  process.exit(result.success ? 0 : 1);
1713
1945
  } catch (error) {
1714
- console.error(chalk6.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1946
+ console.error(chalk7.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1947
+ process.exit(1);
1948
+ }
1949
+ });
1950
+ program.command("remove").description("Remove PWA files and restore HTML files").option("-p, --project-path <path>", "Project path", process.cwd()).option("-o, --output-dir <dir>", "Output directory (auto-detected if not specified)").option("--skip-html-restore", "Skip HTML file restoration").option("--skip-files", "Skip PWA file removal").option("--force", "Force removal without confirmation").action(async (options) => {
1951
+ try {
1952
+ const result = await removeCommand({
1953
+ projectPath: options.projectPath,
1954
+ outputDir: options.outputDir,
1955
+ skipHtmlRestore: options.skipHtmlRestore,
1956
+ skipFiles: options.skipFiles,
1957
+ force: options.force
1958
+ });
1959
+ process.exit(result.success ? 0 : 1);
1960
+ } catch (error) {
1961
+ console.error(chalk7.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1715
1962
  process.exit(1);
1716
1963
  }
1717
1964
  });