@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/esm-BSZHJOVR.js +792 -0
- package/dist/index.cjs +1243 -131
- package/dist/index.js +376 -129
- package/package.json +2 -2
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
1159
|
-
import { join as
|
|
1160
|
-
import
|
|
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
|
|
1164
|
-
import { join as
|
|
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 =
|
|
1168
|
-
if (
|
|
1406
|
+
const packageJsonPath = join6(projectPath, "package.json");
|
|
1407
|
+
if (existsSync6(packageJsonPath)) {
|
|
1169
1408
|
try {
|
|
1170
|
-
const packageContent = JSON.parse(
|
|
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 =
|
|
1187
|
-
if (
|
|
1425
|
+
const composerJsonPath = join6(projectPath, "composer.json");
|
|
1426
|
+
if (existsSync6(composerJsonPath)) {
|
|
1188
1427
|
try {
|
|
1189
|
-
const composerContent = JSON.parse(
|
|
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 =
|
|
1238
|
-
if (
|
|
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 =
|
|
1306
|
-
const buildDir =
|
|
1307
|
-
const publicDir =
|
|
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 (
|
|
1549
|
+
if (existsSync6(distDir)) {
|
|
1311
1550
|
outputDir = "dist";
|
|
1312
1551
|
reason = "dist/ directory detected (production build)";
|
|
1313
|
-
} else if (
|
|
1552
|
+
} else if (existsSync6(buildDir)) {
|
|
1314
1553
|
outputDir = "build";
|
|
1315
1554
|
reason = "build/ directory detected (production build)";
|
|
1316
|
-
} else if (
|
|
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(
|
|
1647
|
+
console.log(chalk6.blue("\n\u{1F4CB} Configuration PWA\n"));
|
|
1349
1648
|
if (suggestions.name.confidence === "high") {
|
|
1350
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
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:
|
|
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:
|
|
1405
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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(
|
|
1608
|
-
const scanResult = await
|
|
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(
|
|
1614
|
-
console.log(
|
|
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(
|
|
1630
|
-
console.log(
|
|
1631
|
-
console.log(
|
|
1632
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
1684
|
-
const result = await
|
|
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(
|
|
1921
|
+
console.log(chalk7.green(`
|
|
1690
1922
|
\u2713 Framework: ${result.framework.framework ?? "Unknown"}`));
|
|
1691
|
-
console.log(
|
|
1692
|
-
console.log(
|
|
1693
|
-
console.log(
|
|
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(
|
|
1696
|
-
console.log(
|
|
1697
|
-
console.log(
|
|
1698
|
-
console.log(
|
|
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(
|
|
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(
|
|
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
|
});
|