laxy-verify 1.1.29 → 1.1.31
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 +7 -8
- package/dist/cli.js +8 -8
- package/dist/crawler.js +20 -21
- package/dist/detect.js +18 -0
- package/dist/e2e.js +9 -14
- package/dist/entitlement.js +4 -4
- package/dist/report-markdown.js +7 -20
- package/dist/verification-core/report.js +1 -1
- package/dist/verification-core/tier-policy.js +12 -23
- package/package.json +67 -67
package/README.md
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
`laxy-verify` is a deployment blocker gate for frontend apps.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
**Pro**: GitHub Actions auto-runs it on every PR + Slack/Discord alerts when things break.
|
|
5
|
+
Every verification run includes the same build, Lighthouse, E2E, multi-viewport, security, visual diff, and stability coverage regardless of plan.
|
|
7
6
|
|
|
8
7
|
## Quick start
|
|
9
8
|
|
|
@@ -25,7 +24,7 @@ That creates:
|
|
|
25
24
|
- `.laxy.yml`
|
|
26
25
|
- `.github/workflows/laxy-verify.yml`
|
|
27
26
|
|
|
28
|
-
|
|
27
|
+
Optional: log in to connect the CLI to your Laxy account:
|
|
29
28
|
|
|
30
29
|
```bash
|
|
31
30
|
npx laxy-verify login
|
|
@@ -55,7 +54,7 @@ The gap is not "can these tools exist together?" The gap is "who turns that pile
|
|
|
55
54
|
- one command instead of a custom build plus audit plus smoke-check script stack
|
|
56
55
|
- one result file instead of scattered logs
|
|
57
56
|
- one blocker-first decision instead of "build passed, but do we actually trust this release?"
|
|
58
|
-
-
|
|
57
|
+
- optional account-linked automation on top of the same verification run
|
|
59
58
|
|
|
60
59
|
This is most useful if you ship frontend apps and want a practical gate before:
|
|
61
60
|
|
|
@@ -84,7 +83,7 @@ A standard run includes:
|
|
|
84
83
|
- multi-viewport checks (desktop, tablet, mobile)
|
|
85
84
|
- blocker-aware reporting and release decisions
|
|
86
85
|
|
|
87
|
-
|
|
86
|
+
Every run includes:
|
|
88
87
|
|
|
89
88
|
- security audit
|
|
90
89
|
- visual diff evidence
|
|
@@ -119,7 +118,7 @@ That creates:
|
|
|
119
118
|
- `.laxy.yml`
|
|
120
119
|
- `.github/workflows/laxy-verify.yml`
|
|
121
120
|
|
|
122
|
-
|
|
121
|
+
Optional: log in to connect the CLI to your Laxy account:
|
|
123
122
|
|
|
124
123
|
```bash
|
|
125
124
|
npx laxy-verify login
|
|
@@ -166,7 +165,7 @@ Artifacts:
|
|
|
166
165
|
- What would block a client demo or QA handoff?
|
|
167
166
|
- Is there enough evidence to merge or release with confidence?
|
|
168
167
|
|
|
169
|
-
|
|
168
|
+
All verification checks already run without a paid plan gate.
|
|
170
169
|
|
|
171
170
|
## Grades
|
|
172
171
|
|
|
@@ -242,7 +241,7 @@ Subcommands:
|
|
|
242
241
|
whoami
|
|
243
242
|
```
|
|
244
243
|
|
|
245
|
-
`--plan-override` is
|
|
244
|
+
`--plan-override` is only for plan-label and automation testing. Verification coverage stays the same on every plan.
|
|
246
245
|
|
|
247
246
|
## Result files
|
|
248
247
|
|
package/dist/cli.js
CHANGED
|
@@ -65,7 +65,7 @@ function shouldFailVerificationResult(report, failOn) {
|
|
|
65
65
|
return false;
|
|
66
66
|
if (report.verdict === "build-failed" || report.verdict === "hold")
|
|
67
67
|
return true;
|
|
68
|
-
if (report.
|
|
68
|
+
if (report.verdict === "investigate")
|
|
69
69
|
return true;
|
|
70
70
|
return (0, grade_js_1.isWorseOrEqual)(report.grade, failOn);
|
|
71
71
|
}
|
|
@@ -281,10 +281,10 @@ async function run() {
|
|
|
281
281
|
npx laxy-verify [project-dir] [options]
|
|
282
282
|
npx laxy-verify <subcommand>
|
|
283
283
|
|
|
284
|
-
Subcommands:
|
|
285
|
-
login [email] Log in to
|
|
286
|
-
logout Remove saved credentials
|
|
287
|
-
whoami Show current login status
|
|
284
|
+
Subcommands:
|
|
285
|
+
login [email] Log in to connect this CLI to your Laxy account
|
|
286
|
+
logout Remove saved credentials
|
|
287
|
+
whoami Show current login status
|
|
288
288
|
|
|
289
289
|
Options:
|
|
290
290
|
--init Generate .laxy.yml + GitHub workflow file
|
|
@@ -294,8 +294,8 @@ async function run() {
|
|
|
294
294
|
--config <path> Path to .laxy.yml
|
|
295
295
|
--fail-on unverified | bronze | silver | gold
|
|
296
296
|
--skip-lighthouse Skip Lighthouse but still run build and E2E
|
|
297
|
-
--plan-override free | pro | team (
|
|
298
|
-
--multi-viewport
|
|
297
|
+
--plan-override free | pro | team (testing metadata only)
|
|
298
|
+
--multi-viewport Lighthouse on desktop/tablet/mobile
|
|
299
299
|
--crawl Crawl the app to discover routes before E2E
|
|
300
300
|
--badge Print shields.io badge markdown
|
|
301
301
|
--help Show this help
|
|
@@ -430,7 +430,7 @@ async function run() {
|
|
|
430
430
|
if (args.planOverride) {
|
|
431
431
|
try {
|
|
432
432
|
effectiveFeatures = (0, entitlement_js_1.applyPlanOverride)(features, args.planOverride);
|
|
433
|
-
console.log(` Plan override: ${(0, entitlement_js_1.normalizePlan)(features.plan)} -> ${effectiveFeatures.plan} (
|
|
433
|
+
console.log(` Plan override: ${(0, entitlement_js_1.normalizePlan)(features.plan)} -> ${effectiveFeatures.plan} (verification behavior is unchanged)`);
|
|
434
434
|
}
|
|
435
435
|
catch (overrideErr) {
|
|
436
436
|
console.error(`Plan override error: ${overrideErr instanceof Error ? overrideErr.message : String(overrideErr)}`);
|
package/dist/crawler.js
CHANGED
|
@@ -219,8 +219,9 @@ async function crawlApp(baseUrl, options) {
|
|
|
219
219
|
* Generate E2E scenarios from crawl results.
|
|
220
220
|
*/
|
|
221
221
|
function buildScenariosFromCrawl(crawlResult, tier) {
|
|
222
|
+
void tier;
|
|
222
223
|
const scenarios = [];
|
|
223
|
-
const limit =
|
|
224
|
+
const limit = 6;
|
|
224
225
|
// Scenario 1: Root page render
|
|
225
226
|
const rootPage = crawlResult.pages.find((p) => p.path === "/");
|
|
226
227
|
if (rootPage) {
|
|
@@ -310,30 +311,28 @@ function buildScenariosFromCrawl(crawlResult, tier) {
|
|
|
310
311
|
],
|
|
311
312
|
});
|
|
312
313
|
}
|
|
313
|
-
// Scenario: Button interactions
|
|
314
|
-
|
|
315
|
-
|
|
314
|
+
// Scenario: Button interactions
|
|
315
|
+
for (const page of crawlResult.pages) {
|
|
316
|
+
if (scenarios.length >= limit)
|
|
317
|
+
break;
|
|
318
|
+
const nonFormButtons = page.buttons.filter((b) => !b.includes("submit")).slice(0, 1);
|
|
319
|
+
for (const btnSelector of nonFormButtons) {
|
|
316
320
|
if (scenarios.length >= limit)
|
|
317
321
|
break;
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
steps.push({
|
|
325
|
-
type: "goto",
|
|
326
|
-
gotoUrl: page.path,
|
|
327
|
-
description: `Navigate to ${page.path}`,
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
steps.push({ type: "check_visible", selector: btnSelector, description: `Button should be visible` }, { type: "click", selector: btnSelector, description: `Click ${btnSelector}` }, { type: "wait", duration: 800, description: "Wait for response" }, { type: "check_visible", selector: "body", description: "Page should remain stable" });
|
|
331
|
-
scenarios.push({
|
|
332
|
-
name: `Button interaction on ${page.path}`,
|
|
333
|
-
steps: steps.slice(0, 5),
|
|
334
|
-
initialUrl: page.path !== "/" ? page.path : undefined,
|
|
322
|
+
const steps = [];
|
|
323
|
+
if (page.path !== "/") {
|
|
324
|
+
steps.push({
|
|
325
|
+
type: "goto",
|
|
326
|
+
gotoUrl: page.path,
|
|
327
|
+
description: `Navigate to ${page.path}`,
|
|
335
328
|
});
|
|
336
329
|
}
|
|
330
|
+
steps.push({ type: "check_visible", selector: btnSelector, description: `Button should be visible` }, { type: "click", selector: btnSelector, description: `Click ${btnSelector}` }, { type: "wait", duration: 800, description: "Wait for response" }, { type: "check_visible", selector: "body", description: "Page should remain stable" });
|
|
331
|
+
scenarios.push({
|
|
332
|
+
name: `Button interaction on ${page.path}`,
|
|
333
|
+
steps: steps.slice(0, 5),
|
|
334
|
+
initialUrl: page.path !== "/" ? page.path : undefined,
|
|
335
|
+
});
|
|
337
336
|
}
|
|
338
337
|
}
|
|
339
338
|
return scenarios.slice(0, limit);
|
package/dist/detect.js
CHANGED
|
@@ -42,6 +42,21 @@ const FRAMEWORK_DEFAULT_PORTS = {
|
|
|
42
42
|
cra: 3000,
|
|
43
43
|
sveltekit: 5173,
|
|
44
44
|
};
|
|
45
|
+
function isLaxyVerifyPackage(pkg) {
|
|
46
|
+
if (pkg.name === "laxy-verify")
|
|
47
|
+
return true;
|
|
48
|
+
const bin = pkg.bin;
|
|
49
|
+
if (typeof bin === "object" && bin !== null) {
|
|
50
|
+
for (const value of Object.values(bin)) {
|
|
51
|
+
if (typeof value === "string" && value.includes("dist/cli.js")) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const scripts = pkg.scripts ?? {};
|
|
57
|
+
return Object.values(scripts).some((script) => typeof script === "string" &&
|
|
58
|
+
(script.includes("node dist/cli.js") || script.includes("laxy-verify")));
|
|
59
|
+
}
|
|
45
60
|
function detectPackageManager(dir) {
|
|
46
61
|
if (fs.existsSync(path.join(dir, "pnpm-lock.yaml")))
|
|
47
62
|
return "pnpm";
|
|
@@ -114,6 +129,9 @@ function detect(dir) {
|
|
|
114
129
|
throw new Error(`Not a Node.js project: no package.json found at ${pkgPath}`);
|
|
115
130
|
}
|
|
116
131
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
132
|
+
if (isLaxyVerifyPackage(pkg)) {
|
|
133
|
+
throw new Error("Cannot run laxy-verify against the laxy-verify package itself. Run it from the frontend app you actually want to verify.");
|
|
134
|
+
}
|
|
117
135
|
if (!pkg.scripts || !pkg.scripts.build) {
|
|
118
136
|
throw new Error("No 'build' script found in package.json");
|
|
119
137
|
}
|
package/dist/e2e.js
CHANGED
|
@@ -37,14 +37,8 @@ function pickSelector(candidates, patterns) {
|
|
|
37
37
|
return undefined;
|
|
38
38
|
}
|
|
39
39
|
function getScenarioLimit(tier) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return 4;
|
|
43
|
-
case "team":
|
|
44
|
-
return 5;
|
|
45
|
-
default:
|
|
46
|
-
return 2;
|
|
47
|
-
}
|
|
40
|
+
void tier;
|
|
41
|
+
return 5;
|
|
48
42
|
}
|
|
49
43
|
function getVisibleAnchor(selectors) {
|
|
50
44
|
return pickSelector(selectors, [/^main$/, /^form$/, /^section$/, /^h1$/, /^h2$/, /^button/, /^a\[href/]) || "body";
|
|
@@ -89,14 +83,15 @@ function getRequiredFillTarget(selectors) {
|
|
|
89
83
|
]);
|
|
90
84
|
}
|
|
91
85
|
function getVerificationCoverageGaps(scenarios, tier) {
|
|
86
|
+
void tier;
|
|
92
87
|
const names = new Set(scenarios.map((scenario) => scenario.name));
|
|
93
88
|
const gaps = [];
|
|
94
89
|
const hasPrimaryAction = names.has("Primary form interaction") || names.has("Primary CTA interaction");
|
|
95
|
-
if (
|
|
90
|
+
if (!hasPrimaryAction) {
|
|
96
91
|
gaps.push("No primary action scenario was detected, so the verify run could not validate a real user action.");
|
|
97
92
|
}
|
|
98
|
-
if (
|
|
99
|
-
gaps.push("Too few meaningful scenarios were detected for a
|
|
93
|
+
if (scenarios.length < 4) {
|
|
94
|
+
gaps.push("Too few meaningful scenarios were detected for a full verification pass, so this run stayed shallower than expected.");
|
|
100
95
|
}
|
|
101
96
|
return gaps;
|
|
102
97
|
}
|
|
@@ -145,7 +140,7 @@ function buildVerifyScenarios(snapshot, tier) {
|
|
|
145
140
|
],
|
|
146
141
|
});
|
|
147
142
|
}
|
|
148
|
-
if (
|
|
143
|
+
if (fillTarget && clickTarget && (requiredFillTarget || feedbackTarget)) {
|
|
149
144
|
scenarios.push({
|
|
150
145
|
name: "Validation feedback",
|
|
151
146
|
steps: [
|
|
@@ -156,7 +151,7 @@ function buildVerifyScenarios(snapshot, tier) {
|
|
|
156
151
|
],
|
|
157
152
|
});
|
|
158
153
|
}
|
|
159
|
-
if (
|
|
154
|
+
if (localLinkTarget) {
|
|
160
155
|
scenarios.push({
|
|
161
156
|
name: "Internal navigation",
|
|
162
157
|
steps: [
|
|
@@ -168,7 +163,7 @@ function buildVerifyScenarios(snapshot, tier) {
|
|
|
168
163
|
],
|
|
169
164
|
});
|
|
170
165
|
}
|
|
171
|
-
if (
|
|
166
|
+
if (clickTarget && fillTarget && clickTarget !== fillTarget) {
|
|
172
167
|
scenarios.push({
|
|
173
168
|
name: "Repeated interaction stability",
|
|
174
169
|
steps: [
|
package/dist/entitlement.js
CHANGED
|
@@ -5,11 +5,11 @@ exports.getEntitlements = getEntitlements;
|
|
|
5
5
|
exports.applyPlanOverride = applyPlanOverride;
|
|
6
6
|
exports.printPlanBanner = printPlanBanner;
|
|
7
7
|
/**
|
|
8
|
-
* Fetches
|
|
8
|
+
* Fetches account entitlements for laxy-verify.
|
|
9
9
|
*
|
|
10
|
-
* The CLI asks /api/v1/cli-entitlement for the current plan and enabled
|
|
10
|
+
* The CLI asks /api/v1/cli-entitlement for the current plan and enabled automation flags.
|
|
11
11
|
* Responses are cached briefly to avoid repeated network calls during a single run.
|
|
12
|
-
* If the request fails or the user is not logged in, the CLI safely falls back to
|
|
12
|
+
* If the request fails or the user is not logged in, the CLI safely falls back to the unlocked verification defaults.
|
|
13
13
|
*/
|
|
14
14
|
const auth_js_1 = require("./auth.js");
|
|
15
15
|
const FREE_FEATURES = {
|
|
@@ -49,7 +49,7 @@ async function getEntitlements() {
|
|
|
49
49
|
});
|
|
50
50
|
if (!res.ok) {
|
|
51
51
|
if (res.status === 401) {
|
|
52
|
-
console.error(" Note: your CLI session is no longer valid. Run laxy-verify login again to refresh your
|
|
52
|
+
console.error(" Note: your CLI session is no longer valid. Run laxy-verify login again to refresh your account metadata.");
|
|
53
53
|
}
|
|
54
54
|
return FREE_FEATURES;
|
|
55
55
|
}
|
package/dist/report-markdown.js
CHANGED
|
@@ -60,30 +60,19 @@ function formatTimestamp(iso) {
|
|
|
60
60
|
return date.toISOString().replace("T", " ").replace(".000Z", " UTC");
|
|
61
61
|
}
|
|
62
62
|
function sentenceForVerdict(view) {
|
|
63
|
-
const isReleaseTier = view.tier === "team";
|
|
64
63
|
switch (view.verdict) {
|
|
65
64
|
case "client-ready":
|
|
66
65
|
return "Yes. This run collected enough evidence to support a client-ready call.";
|
|
67
66
|
case "release-ready":
|
|
68
|
-
return
|
|
69
|
-
? "Yes. This run collected enough evidence to support a release-ready call."
|
|
70
|
-
: "Yes. The current build looks strong enough to hand to a client.";
|
|
67
|
+
return "Yes. This run collected enough evidence to support a release-ready call.";
|
|
71
68
|
case "hold":
|
|
72
|
-
return
|
|
73
|
-
? "No. This run found blockers that should be fixed before release."
|
|
74
|
-
: "No. This run found blockers that should be fixed before sending this to a client.";
|
|
69
|
+
return "No. This run found blockers that should be fixed before release.";
|
|
75
70
|
case "investigate":
|
|
76
|
-
return
|
|
77
|
-
? "Not yet. The project is standing, but there is not enough confidence to call it release-ready."
|
|
78
|
-
: "Not yet. The project may be usable, but the current evidence is not strong enough for a client handoff.";
|
|
71
|
+
return "Not yet. The project is standing, but there is not enough confidence to call it release-ready.";
|
|
79
72
|
case "build-failed":
|
|
80
|
-
return
|
|
81
|
-
? "No. The production build failed, so the release should be held immediately."
|
|
82
|
-
: "No. The production build failed, so this should not be sent to a client.";
|
|
73
|
+
return "No. The production build failed, so the release should be held immediately.";
|
|
83
74
|
default:
|
|
84
|
-
return
|
|
85
|
-
? "This run did not find an immediate hard blocker, but it is still a shallow release-confidence pass."
|
|
86
|
-
: "This run did not find an immediate hard blocker, but it is still a shallow delivery-confidence pass.";
|
|
75
|
+
return "This run did not find an immediate hard blocker, but it is still a shallow release-confidence pass.";
|
|
87
76
|
}
|
|
88
77
|
}
|
|
89
78
|
function defaultNextActions(result) {
|
|
@@ -98,13 +87,11 @@ function defaultNextActions(result) {
|
|
|
98
87
|
case "release-ready":
|
|
99
88
|
return ["Ship this version, or archive this report as release evidence."];
|
|
100
89
|
case "investigate":
|
|
101
|
-
return
|
|
102
|
-
? ["Collect the missing verification evidence, then rerun the command before release."]
|
|
103
|
-
: ["Collect the missing verification evidence, then rerun the command before sending this to a client."];
|
|
90
|
+
return ["Collect the missing verification evidence, then rerun the command before release."];
|
|
104
91
|
case "build-failed":
|
|
105
92
|
return ["Fix the production build first, then rerun the verification command."];
|
|
106
93
|
case "quick-pass":
|
|
107
|
-
return ["Run
|
|
94
|
+
return ["Run another full verification pass after the next meaningful change."];
|
|
108
95
|
default:
|
|
109
96
|
return ["Rerun verification after the blockers are fixed."];
|
|
110
97
|
}
|
|
@@ -149,7 +149,7 @@ function getImprovementRecommendations(input, thresholds = exports.DEFAULT_LH_TH
|
|
|
149
149
|
severity: "high",
|
|
150
150
|
title: `Verification coverage gaps (${input.e2eCoverageGaps?.length ?? 0})`,
|
|
151
151
|
description: input.e2eCoverageGaps.join(" "),
|
|
152
|
-
action: "Add or expose a stable primary user flow, then rerun verification so the
|
|
152
|
+
action: "Add or expose a stable primary user flow, then rerun verification so the checks cover real interactions.",
|
|
153
153
|
});
|
|
154
154
|
}
|
|
155
155
|
if (input.e2eStabilityPassed === false) {
|
|
@@ -4,30 +4,25 @@ exports.getTierPolicy = getTierPolicy;
|
|
|
4
4
|
exports.planToVerificationTier = planToVerificationTier;
|
|
5
5
|
exports.getVerificationTierQuestion = getVerificationTierQuestion;
|
|
6
6
|
exports.getTierVerificationView = getTierVerificationView;
|
|
7
|
+
const UNLOCKED_POLICY = {
|
|
8
|
+
showDetailedLighthouse: true,
|
|
9
|
+
showDetailedE2E: true,
|
|
10
|
+
showReportExport: true,
|
|
11
|
+
maxBlockers: 10,
|
|
12
|
+
maxWarnings: 10,
|
|
13
|
+
};
|
|
7
14
|
const TIER_POLICIES = {
|
|
8
15
|
free: {
|
|
9
16
|
tier: "free",
|
|
10
|
-
|
|
11
|
-
showDetailedE2E: false,
|
|
12
|
-
showReportExport: false,
|
|
13
|
-
maxBlockers: 1,
|
|
14
|
-
maxWarnings: 2,
|
|
17
|
+
...UNLOCKED_POLICY,
|
|
15
18
|
},
|
|
16
19
|
pro: {
|
|
17
20
|
tier: "pro",
|
|
18
|
-
|
|
19
|
-
showDetailedE2E: true,
|
|
20
|
-
showReportExport: true,
|
|
21
|
-
maxBlockers: 5,
|
|
22
|
-
maxWarnings: 5,
|
|
21
|
+
...UNLOCKED_POLICY,
|
|
23
22
|
},
|
|
24
23
|
team: {
|
|
25
24
|
tier: "team",
|
|
26
|
-
|
|
27
|
-
showDetailedE2E: true,
|
|
28
|
-
showReportExport: true,
|
|
29
|
-
maxBlockers: 10,
|
|
30
|
-
maxWarnings: 10,
|
|
25
|
+
...UNLOCKED_POLICY,
|
|
31
26
|
},
|
|
32
27
|
};
|
|
33
28
|
function getTierPolicy(tier = "free") {
|
|
@@ -41,14 +36,8 @@ function planToVerificationTier(plan) {
|
|
|
41
36
|
return "free";
|
|
42
37
|
}
|
|
43
38
|
function getVerificationTierQuestion(tier) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return "Ready to show a client?";
|
|
47
|
-
case "team":
|
|
48
|
-
return "Ready for team deployment?";
|
|
49
|
-
default:
|
|
50
|
-
return "Any critical issues right now?";
|
|
51
|
-
}
|
|
39
|
+
void tier;
|
|
40
|
+
return "Is this ready to ship?";
|
|
52
41
|
}
|
|
53
42
|
function getTierVerificationView(report) {
|
|
54
43
|
const policy = getTierPolicy(report.tier);
|
package/package.json
CHANGED
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "laxy-verify",
|
|
3
|
+
"version": "1.1.31",
|
|
4
|
+
"description": "Frontend verification CLI for build checks, Lighthouse, E2E, and release readiness",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"homepage": "https://github.com/SUNgm24/Laxy/tree/main/laxy-verify#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/SUNgm24/Laxy.git",
|
|
11
|
+
"directory": "laxy-verify"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/SUNgm24/Laxy/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"frontend",
|
|
18
|
+
"verification",
|
|
19
|
+
"quality-gate",
|
|
20
|
+
"release-readiness",
|
|
21
|
+
"lighthouse",
|
|
22
|
+
"e2e",
|
|
23
|
+
"qa",
|
|
24
|
+
"cli",
|
|
25
|
+
"nextjs",
|
|
26
|
+
"vite"
|
|
27
|
+
],
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20.18.0"
|
|
30
|
+
},
|
|
31
|
+
"bin": {
|
|
32
|
+
"laxy-verify": "dist/cli.js"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist/"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc",
|
|
39
|
+
"start": "node dist/cli.js",
|
|
40
|
+
"test": "vitest run",
|
|
41
|
+
"test:coverage": "vitest run --coverage"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@lhci/cli": "^0.14.0",
|
|
45
|
+
"chrome-launcher": "^0.13.4",
|
|
46
|
+
"js-yaml": "^4.1.0",
|
|
47
|
+
"lighthouse": "^12.1.0",
|
|
48
|
+
"pixelmatch": "^7.1.0",
|
|
49
|
+
"pngjs": "^7.0.0",
|
|
50
|
+
"puppeteer": "^24.40.0",
|
|
51
|
+
"tree-kill": "^1.2.2"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/js-yaml": "^4.0.9",
|
|
55
|
+
"@types/node": "^20.0.0",
|
|
56
|
+
"typescript": "^5.4.0",
|
|
57
|
+
"vitest": "^2.0.0"
|
|
58
|
+
},
|
|
59
|
+
"peerDependencies": {
|
|
60
|
+
"playwright": "^1.40.0"
|
|
61
|
+
},
|
|
62
|
+
"peerDependenciesMeta": {
|
|
63
|
+
"playwright": {
|
|
64
|
+
"optional": true
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|