laxy-verify 1.3.3 → 1.4.1
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/README.md +509 -491
- package/dist/auto-fix.d.ts +66 -0
- package/dist/auto-fix.js +178 -0
- package/dist/check-run.d.ts +58 -0
- package/dist/check-run.js +139 -0
- package/dist/cli.js +153 -13
- package/dist/entitlement.d.ts +2 -0
- package/dist/entitlement.js +4 -0
- package/dist/update-check.d.ts +6 -0
- package/dist/update-check.js +138 -0
- package/package.json +4 -5
package/dist/cli.js
CHANGED
|
@@ -48,6 +48,7 @@ const grade_js_1 = require("./grade.js");
|
|
|
48
48
|
const init_js_1 = require("./init.js");
|
|
49
49
|
const badge_js_1 = require("./badge.js");
|
|
50
50
|
const comment_js_1 = require("./comment.js");
|
|
51
|
+
const check_run_js_1 = require("./check-run.js");
|
|
51
52
|
const status_js_1 = require("./status.js");
|
|
52
53
|
const auth_js_1 = require("./auth.js");
|
|
53
54
|
const entitlement_js_1 = require("./entitlement.js");
|
|
@@ -68,8 +69,10 @@ const vitals_budget_js_1 = require("./vitals-budget.js");
|
|
|
68
69
|
const index_js_1 = require("./verification-core/index.js");
|
|
69
70
|
const trend_js_1 = require("./trend.js");
|
|
70
71
|
const ai_analysis_js_1 = require("./ai-analysis.js");
|
|
72
|
+
const auto_fix_js_1 = require("./auto-fix.js");
|
|
71
73
|
const compare_env_js_1 = require("./compare-env.js");
|
|
72
74
|
const route_discovery_js_1 = require("./route-discovery.js");
|
|
75
|
+
const update_check_js_1 = require("./update-check.js");
|
|
73
76
|
const package_json_1 = __importDefault(require("../package.json"));
|
|
74
77
|
let activeDevServerPid;
|
|
75
78
|
let activeDevServerCleanup = null;
|
|
@@ -173,6 +176,7 @@ function parseArgs() {
|
|
|
173
176
|
a11yDeep: flags["a11y-deep"] !== undefined,
|
|
174
177
|
seoDeep: flags["seo-deep"] !== undefined,
|
|
175
178
|
vitalsBudget: flags["vitals-budget"] !== undefined,
|
|
179
|
+
autoFix: flags["auto-fix"] !== undefined,
|
|
176
180
|
};
|
|
177
181
|
}
|
|
178
182
|
function writeResultFile(projectDir, result) {
|
|
@@ -200,8 +204,10 @@ function uniqueRouteList(routes) {
|
|
|
200
204
|
function summarizeViewportIssues(scores, thresholds) {
|
|
201
205
|
if (!scores)
|
|
202
206
|
return { count: 0 };
|
|
207
|
+
const viewportKeys = ["desktop", "tablet", "mobile"];
|
|
203
208
|
const failed = [];
|
|
204
|
-
for (const
|
|
209
|
+
for (const label of viewportKeys) {
|
|
210
|
+
const viewportScores = scores[label];
|
|
205
211
|
if (!viewportScores) {
|
|
206
212
|
failed.push(`${label}: missing`);
|
|
207
213
|
continue;
|
|
@@ -399,7 +405,7 @@ async function run() {
|
|
|
399
405
|
--config <path> Path to .laxy.yml
|
|
400
406
|
--fail-on unverified | bronze | silver | gold
|
|
401
407
|
--skip-lighthouse Skip Lighthouse but still run build and E2E
|
|
402
|
-
--port <port> Use an already-running dev server on this port (skip build & server start)
|
|
408
|
+
--port <port> Use an already-running dev server on this port (skip build & server start)
|
|
403
409
|
--share Create a public share link for this verification result (Pro)
|
|
404
410
|
--compare <url> Compare Lighthouse scores against a reference environment URL (Pro)
|
|
405
411
|
--plan-override free | pro | team (testing metadata only)
|
|
@@ -413,6 +419,7 @@ async function run() {
|
|
|
413
419
|
--a11y-deep Deep accessibility audit (axe-core)
|
|
414
420
|
--seo-deep Deep SEO audit (meta, OG, JSON-LD)
|
|
415
421
|
--vitals-budget Core Web Vitals budget check (LCP, CLS, INP)
|
|
422
|
+
--auto-fix AI code fix suggestions for blockers (Pro)
|
|
416
423
|
--help Show this help
|
|
417
424
|
|
|
418
425
|
Exit codes:
|
|
@@ -421,10 +428,10 @@ async function run() {
|
|
|
421
428
|
2 Configuration error
|
|
422
429
|
|
|
423
430
|
Examples:
|
|
424
|
-
npx laxy-verify --init --run # Setup + first verification
|
|
425
|
-
npx laxy-verify . # Run in current directory
|
|
426
|
-
npx laxy-verify . # Auto-falls back if port 3000 is already busy
|
|
427
|
-
npx laxy-verify . --ci # CI mode
|
|
431
|
+
npx laxy-verify --init --run # Setup + first verification
|
|
432
|
+
npx laxy-verify . # Run in current directory
|
|
433
|
+
npx laxy-verify . # Auto-falls back if port 3000 is already busy
|
|
434
|
+
npx laxy-verify . --ci # CI mode
|
|
428
435
|
npx laxy-verify . --fail-on silver # Block Bronze or worse
|
|
429
436
|
npx laxy-verify . --port 3001 # Use existing dev server on port 3001
|
|
430
437
|
npx laxy-verify . --share # Save and share a public verification link
|
|
@@ -450,6 +457,35 @@ async function run() {
|
|
|
450
457
|
exitGracefully(0);
|
|
451
458
|
return;
|
|
452
459
|
}
|
|
460
|
+
// Check for updates (skip in CI mode and non-interactive environments)
|
|
461
|
+
if (!args.ciMode && !process.env.CI && process.stdin.isTTY) {
|
|
462
|
+
const updated = await (0, update_check_js_1.checkForUpdates)();
|
|
463
|
+
if (updated) {
|
|
464
|
+
// Re-exec with npx to use the updated version
|
|
465
|
+
const { spawnSync } = await Promise.resolve().then(() => __importStar(require("node:child_process")));
|
|
466
|
+
try {
|
|
467
|
+
const reExecResult = spawnSync("npx", ["laxy-verify", ...process.argv.slice(2)], {
|
|
468
|
+
stdio: "inherit",
|
|
469
|
+
cwd: process.cwd(),
|
|
470
|
+
});
|
|
471
|
+
if (reExecResult.error)
|
|
472
|
+
throw reExecResult.error;
|
|
473
|
+
if (reExecResult.status !== 0) {
|
|
474
|
+
const err = new Error("Re-exec failed");
|
|
475
|
+
err.status = reExecResult.status;
|
|
476
|
+
throw err;
|
|
477
|
+
}
|
|
478
|
+
exitGracefully(0);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
catch (reExecErr) {
|
|
482
|
+
// If re-exec fails, the child already printed errors — propagate exit code
|
|
483
|
+
const code = reExecErr.status ?? 1;
|
|
484
|
+
exitGracefully(code);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
453
489
|
if (args.init) {
|
|
454
490
|
let initEntitlements = null;
|
|
455
491
|
try {
|
|
@@ -625,6 +661,8 @@ async function run() {
|
|
|
625
661
|
compare_env: false,
|
|
626
662
|
share_result: false,
|
|
627
663
|
history_trend: false,
|
|
664
|
+
pr_decoration: false,
|
|
665
|
+
auto_fix: false,
|
|
628
666
|
};
|
|
629
667
|
let effectiveFeatures = features;
|
|
630
668
|
if (args.planOverride) {
|
|
@@ -1134,7 +1172,6 @@ async function run() {
|
|
|
1134
1172
|
if ((0, report_markdown_js_1.shouldWriteMarkdownReport)(resultObj)) {
|
|
1135
1173
|
const markdownReport = (0, report_markdown_js_1.buildMarkdownReport)(args.projectDir, resultObj);
|
|
1136
1174
|
fs.writeFileSync(markdownReportPath, markdownReport, "utf-8");
|
|
1137
|
-
resultObj.markdownReportPath = markdownReportPath;
|
|
1138
1175
|
}
|
|
1139
1176
|
else if (fs.existsSync(markdownReportPath)) {
|
|
1140
1177
|
fs.rmSync(markdownReportPath, { force: true });
|
|
@@ -1142,8 +1179,8 @@ async function run() {
|
|
|
1142
1179
|
const inGitHubActions = !!process.env.GITHUB_ACTIONS;
|
|
1143
1180
|
if (inGitHubActions) {
|
|
1144
1181
|
try {
|
|
1182
|
+
let trendDelta = null;
|
|
1145
1183
|
if (process.env.GITHUB_EVENT_NAME === "pull_request") {
|
|
1146
|
-
let trendDelta = null;
|
|
1147
1184
|
const baseSnapshot = (0, trend_js_1.loadBaseSnapshot)((0, trend_js_1.getBaseResultPath)(args.projectDir));
|
|
1148
1185
|
if (baseSnapshot) {
|
|
1149
1186
|
const currentSnapshot = {
|
|
@@ -1154,11 +1191,41 @@ async function run() {
|
|
|
1154
1191
|
};
|
|
1155
1192
|
trendDelta = (0, trend_js_1.computeTrendDelta)(currentSnapshot, baseSnapshot);
|
|
1156
1193
|
}
|
|
1157
|
-
await (0, comment_js_1.postPRComment)(resultObj, trendDelta);
|
|
1158
|
-
resultObj.github = { status: "comment_posted", grade: resultObj.grade };
|
|
1159
1194
|
}
|
|
1160
|
-
|
|
1161
|
-
|
|
1195
|
+
// Pro: Check Run (rich merge-box integration) + enhanced PR comment
|
|
1196
|
+
if (effectiveFeatures.pr_decoration) {
|
|
1197
|
+
if (process.env.GITHUB_EVENT_NAME === "pull_request") {
|
|
1198
|
+
await (0, check_run_js_1.createCheckRun)({
|
|
1199
|
+
grade: resultObj.grade,
|
|
1200
|
+
verdict: verificationReport.verdict,
|
|
1201
|
+
exitCode: resultObj.exitCode,
|
|
1202
|
+
lighthouse: resultObj.lighthouse,
|
|
1203
|
+
thresholds: adjustedThresholds,
|
|
1204
|
+
e2e: resultObj.e2e,
|
|
1205
|
+
security: securityAuditResult ? { critical: securityAuditResult.critical, high: securityAuditResult.high, total: securityAuditResult.totalVulnerabilities } : undefined,
|
|
1206
|
+
build: { success: buildResult.success, durationMs: buildResult.durationMs },
|
|
1207
|
+
blockers: verificationView.blockers.slice(0, 10).map((b) => ({ title: b.title, severity: b.severity })),
|
|
1208
|
+
typecheck: typecheckResult ? { errorCount: typecheckResult.errorCount } : undefined,
|
|
1209
|
+
secretScan: secretScanResult ? { findingsCount: secretScanResult.findings.length } : undefined,
|
|
1210
|
+
bundleSize: bundleSizeResult ? { advisory: bundleSizeResult.advisory } : undefined,
|
|
1211
|
+
a11yDeep: a11yDeepResult ? { criticalCount: a11yDeepResult.criticalCount, seriousCount: a11yDeepResult.seriousCount } : undefined,
|
|
1212
|
+
brokenLinks: brokenLinksResult ? { broken: brokenLinksResult.brokenLinks.length, checked: brokenLinksResult.checkedCount } : undefined,
|
|
1213
|
+
consoleErrors: e2eConsoleErrors.length,
|
|
1214
|
+
config_fail_on: config.fail_on,
|
|
1215
|
+
}, trendDelta);
|
|
1216
|
+
await (0, comment_js_1.postPRComment)(resultObj, trendDelta);
|
|
1217
|
+
resultObj.github = { status: "check_run_created", grade: resultObj.grade };
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
else {
|
|
1221
|
+
// Free: basic status check + simple PR comment
|
|
1222
|
+
if (process.env.GITHUB_EVENT_NAME === "pull_request") {
|
|
1223
|
+
await (0, comment_js_1.postPRComment)(resultObj, trendDelta);
|
|
1224
|
+
resultObj.github = { status: "comment_posted", grade: resultObj.grade };
|
|
1225
|
+
}
|
|
1226
|
+
await (0, status_js_1.createStatusCheck)({ grade: resultObj.grade, exitCode: resultObj.exitCode });
|
|
1227
|
+
resultObj.github ??= { status: "status_set", grade: resultObj.grade };
|
|
1228
|
+
}
|
|
1162
1229
|
}
|
|
1163
1230
|
catch (ghErr) {
|
|
1164
1231
|
console.error(`GitHub API warning: ${ghErr instanceof Error ? ghErr.message : String(ghErr)}`);
|
|
@@ -1263,6 +1330,79 @@ async function run() {
|
|
|
1263
1330
|
// AI analysis errors are non-critical
|
|
1264
1331
|
}
|
|
1265
1332
|
}
|
|
1333
|
+
// Pro: AI Auto-Fix — collect source files, request code fixes, optionally apply
|
|
1334
|
+
if (effectiveFeatures.auto_fix &&
|
|
1335
|
+
args.autoFix &&
|
|
1336
|
+
verificationReport.verdict !== "client-ready" &&
|
|
1337
|
+
verificationReport.verdict !== "release-ready" &&
|
|
1338
|
+
args.format !== "json") {
|
|
1339
|
+
try {
|
|
1340
|
+
console.log("\n Requesting AI Auto-Fix...");
|
|
1341
|
+
console.log(" Note: Source files will be sent to an external AI provider for analysis.");
|
|
1342
|
+
const fileSnippets = (0, auto_fix_js_1.collectFileSnippets)(args.projectDir, buildResult.errors);
|
|
1343
|
+
const autoFixResult = await (0, auto_fix_js_1.requestAutoFix)({
|
|
1344
|
+
grade: unifiedGrade,
|
|
1345
|
+
blockers: verificationView.blockers.map((b) => ({
|
|
1346
|
+
title: b.title,
|
|
1347
|
+
severity: b.severity,
|
|
1348
|
+
action: b.action,
|
|
1349
|
+
})),
|
|
1350
|
+
lighthouseScores: scores ?? null,
|
|
1351
|
+
thresholds: adjustedThresholds,
|
|
1352
|
+
buildErrors: buildResult.errors.slice(0, 3),
|
|
1353
|
+
e2eFailed: e2eResult ? e2eResult.failed : 0,
|
|
1354
|
+
e2eTotal: e2eResult ? e2eResult.total : 0,
|
|
1355
|
+
securitySummary: securityAuditResult?.summary,
|
|
1356
|
+
fileSnippets,
|
|
1357
|
+
});
|
|
1358
|
+
if (autoFixResult && autoFixResult.fixes.length > 0) {
|
|
1359
|
+
console.log(`\n AI Auto-Fix (Pro) — confidence: ${autoFixResult.confidence}`);
|
|
1360
|
+
console.log(` Root cause: ${autoFixResult.rootCause}`);
|
|
1361
|
+
console.log(` Suggested fixes:`);
|
|
1362
|
+
for (const fix of autoFixResult.fixes) {
|
|
1363
|
+
console.log(` - ${fix.filePath}: ${fix.description} (${fix.type})`);
|
|
1364
|
+
}
|
|
1365
|
+
// In CI with GITHUB_ACTIONS: write fixes as patch files for PR creation
|
|
1366
|
+
if (inGitHubActions) {
|
|
1367
|
+
const patchDir = path.join(args.projectDir, ".laxy-fixes");
|
|
1368
|
+
fs.mkdirSync(patchDir, { recursive: true });
|
|
1369
|
+
for (const fix of autoFixResult.fixes) {
|
|
1370
|
+
const fixPath = path.join(patchDir, fix.filePath.replace(/\//g, "_") + ".patch.json");
|
|
1371
|
+
fs.writeFileSync(fixPath, JSON.stringify(fix, null, 2), "utf-8");
|
|
1372
|
+
}
|
|
1373
|
+
console.log(` Fixes written to .laxy-fixes/ for PR automation.`);
|
|
1374
|
+
}
|
|
1375
|
+
else {
|
|
1376
|
+
// Interactive: apply fixes to local project
|
|
1377
|
+
console.log(`\n Applying fixes to local project...`);
|
|
1378
|
+
let applied = 0;
|
|
1379
|
+
for (const fix of autoFixResult.fixes) {
|
|
1380
|
+
const result = (0, auto_fix_js_1.applyCodeFix)(args.projectDir, fix);
|
|
1381
|
+
if (result.success) {
|
|
1382
|
+
console.log(` ✓ ${fix.filePath}`);
|
|
1383
|
+
applied++;
|
|
1384
|
+
}
|
|
1385
|
+
else {
|
|
1386
|
+
console.log(` ✗ ${fix.filePath}: ${result.error}`);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
if (applied > 0) {
|
|
1390
|
+
console.log(`\n ${applied}/${autoFixResult.fixes.length} fix(es) applied. Rerun verification to check results.`);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
else if (autoFixResult) {
|
|
1395
|
+
console.log(`\n AI Auto-Fix: ${autoFixResult.rootCause}`);
|
|
1396
|
+
console.log(` No concrete code fixes could be generated (confidence: ${autoFixResult.confidence}).`);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
catch {
|
|
1400
|
+
// Auto-fix errors are non-critical
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
else if (args.autoFix && !effectiveFeatures.auto_fix) {
|
|
1404
|
+
console.log("\n [warn] --auto-fix requires a logged-in Pro account. Skipping AI auto-fix.");
|
|
1405
|
+
}
|
|
1266
1406
|
if (args.format === "json") {
|
|
1267
1407
|
console.log(JSON.stringify(resultObj, null, 2));
|
|
1268
1408
|
}
|
|
@@ -1276,7 +1416,7 @@ async function run() {
|
|
|
1276
1416
|
}
|
|
1277
1417
|
if (!effectiveFeatures.history_trend) {
|
|
1278
1418
|
console.log(`\n Tip: Pro tracks your last 30 runs so you can see if Performance or Grade is improving.`);
|
|
1279
|
-
console.log(` https://laxy.app/pricing`);
|
|
1419
|
+
console.log(` https://laxy-blue.vercel.app/pricing`);
|
|
1280
1420
|
}
|
|
1281
1421
|
}
|
|
1282
1422
|
if (inGitHubActions && process.env.GITHUB_OUTPUT) {
|
package/dist/entitlement.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export interface EntitlementFeatures {
|
|
|
7
7
|
compare_env: boolean;
|
|
8
8
|
share_result: boolean;
|
|
9
9
|
history_trend: boolean;
|
|
10
|
+
pr_decoration: boolean;
|
|
11
|
+
auto_fix: boolean;
|
|
10
12
|
}
|
|
11
13
|
export type TestablePlan = "free" | "pro" | "team";
|
|
12
14
|
export declare function normalizePlan(plan?: string | null): TestablePlan;
|
package/dist/entitlement.js
CHANGED
|
@@ -22,6 +22,8 @@ const FREE_FEATURES = {
|
|
|
22
22
|
compare_env: false,
|
|
23
23
|
share_result: false,
|
|
24
24
|
history_trend: false,
|
|
25
|
+
pr_decoration: false,
|
|
26
|
+
auto_fix: false,
|
|
25
27
|
};
|
|
26
28
|
let cache = null;
|
|
27
29
|
const CACHE_TTL_MS = 5 * 60 * 1000;
|
|
@@ -80,6 +82,8 @@ function applyPlanOverride(features, overridePlan) {
|
|
|
80
82
|
compare_env: isPro,
|
|
81
83
|
share_result: isPro,
|
|
82
84
|
history_trend: isPro,
|
|
85
|
+
pr_decoration: isPro,
|
|
86
|
+
auto_fix: isPro,
|
|
83
87
|
// queue_priority, parallel_execution — Team only
|
|
84
88
|
queue_priority: isTeam,
|
|
85
89
|
parallel_execution: isTeam,
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.checkForUpdates = checkForUpdates;
|
|
37
|
+
/**
|
|
38
|
+
* Check for newer versions of laxy-verify on npm.
|
|
39
|
+
* Prompts the user to update if a newer version is available.
|
|
40
|
+
*/
|
|
41
|
+
const node_child_process_1 = require("node:child_process");
|
|
42
|
+
const fs = __importStar(require("node:fs"));
|
|
43
|
+
const readline = __importStar(require("node:readline"));
|
|
44
|
+
const PACKAGE_NAME = "laxy-verify";
|
|
45
|
+
function getInstalledVersion() {
|
|
46
|
+
const pkgPath = require.resolve(`${PACKAGE_NAME}/package.json`);
|
|
47
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
48
|
+
return pkg.version;
|
|
49
|
+
}
|
|
50
|
+
function getLatestVersion() {
|
|
51
|
+
try {
|
|
52
|
+
const result = (0, node_child_process_1.execSync)(`npm view ${PACKAGE_NAME} version`, {
|
|
53
|
+
encoding: "utf-8",
|
|
54
|
+
timeout: 10_000,
|
|
55
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
56
|
+
});
|
|
57
|
+
return result.trim();
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function compareVersions(installed, latest) {
|
|
64
|
+
const a = installed.split(".").map(Number);
|
|
65
|
+
const b = latest.split(".").map(Number);
|
|
66
|
+
for (let i = 0; i < 3; i++) {
|
|
67
|
+
if ((a[i] ?? 0) < (b[i] ?? 0))
|
|
68
|
+
return -1;
|
|
69
|
+
if ((a[i] ?? 0) > (b[i] ?? 0))
|
|
70
|
+
return 1;
|
|
71
|
+
}
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
function promptUser(question) {
|
|
75
|
+
const rl = readline.createInterface({
|
|
76
|
+
input: process.stdin,
|
|
77
|
+
output: process.stdout,
|
|
78
|
+
});
|
|
79
|
+
return new Promise((resolve) => {
|
|
80
|
+
rl.question(question, (answer) => {
|
|
81
|
+
rl.close();
|
|
82
|
+
resolve(answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes");
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function runUpdate() {
|
|
87
|
+
try {
|
|
88
|
+
console.log(` Updating ${PACKAGE_NAME}...`);
|
|
89
|
+
(0, node_child_process_1.execSync)(`npm install -g ${PACKAGE_NAME}@latest`, {
|
|
90
|
+
encoding: "utf-8",
|
|
91
|
+
timeout: 60_000,
|
|
92
|
+
stdio: "inherit",
|
|
93
|
+
});
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Global install failed — try local update
|
|
98
|
+
try {
|
|
99
|
+
(0, node_child_process_1.execSync)(`npm install ${PACKAGE_NAME}@latest`, {
|
|
100
|
+
encoding: "utf-8",
|
|
101
|
+
timeout: 60_000,
|
|
102
|
+
stdio: "inherit",
|
|
103
|
+
});
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Checks for updates and prompts the user.
|
|
113
|
+
* Returns true if the user updated (caller should re-exec with npx).
|
|
114
|
+
* Returns false if no update needed or user declined.
|
|
115
|
+
*/
|
|
116
|
+
async function checkForUpdates() {
|
|
117
|
+
const installed = getInstalledVersion();
|
|
118
|
+
const latest = getLatestVersion();
|
|
119
|
+
if (!latest)
|
|
120
|
+
return false;
|
|
121
|
+
if (compareVersions(installed, latest) >= 0)
|
|
122
|
+
return false;
|
|
123
|
+
console.log(`\n laxy-verify update available: ${installed} → ${latest}`);
|
|
124
|
+
const shouldUpdate = await promptUser(" Update now? (y/N): ");
|
|
125
|
+
if (!shouldUpdate) {
|
|
126
|
+
console.log(" Skipping update.\n");
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
const success = runUpdate();
|
|
130
|
+
if (success) {
|
|
131
|
+
console.log(` Updated to ${PACKAGE_NAME}@${latest}. Re-running verification...\n`);
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
console.error(` Update failed. Continuing with ${installed}.\n`);
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
package/package.json
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "laxy-verify",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Frontend verification CLI for build checks, Lighthouse, E2E, and release readiness",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
7
|
-
"homepage": "https://github.com/SUNgm24/
|
|
7
|
+
"homepage": "https://github.com/SUNgm24/laxy-verify#readme",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/SUNgm24/
|
|
11
|
-
"directory": "laxy-verify"
|
|
10
|
+
"url": "git+https://github.com/SUNgm24/laxy-verify.git"
|
|
12
11
|
},
|
|
13
12
|
"bugs": {
|
|
14
|
-
"url": "https://github.com/SUNgm24/
|
|
13
|
+
"url": "https://github.com/SUNgm24/laxy-verify/issues"
|
|
15
14
|
},
|
|
16
15
|
"keywords": [
|
|
17
16
|
"frontend",
|