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 CHANGED
@@ -2,8 +2,7 @@
2
2
 
3
3
  `laxy-verify` is a deployment blocker gate for frontend apps.
4
4
 
5
- **Free**: Run verification manually on any project.
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
- Log in to unlock Pro features:
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
- - **Pro**: GitHub Actions auto-run + Slack/Discord alerts so you never miss a failure
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
- With a logged-in account, additional checks run:
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
- Log in to unlock paid plan features:
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
- Log in to unlock deeper checks (security audit, visual diff, stability pass).
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 for downgrade testing only. It can simulate lower tiers, but it will not let you exceed your real entitlement.
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.tier === "team" && report.verdict === "investigate")
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 unlock deeper reports and release evidence
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 (downgrade testing only)
298
- --multi-viewport team: Lighthouse on desktop/tablet/mobile
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} (testing lower-tier verification behavior)`);
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 = tier === "team" ? 6 : tier === "pro" ? 4 : 2;
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 (Team only)
314
- if (tier === "team") {
315
- for (const page of crawlResult.pages) {
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 nonFormButtons = page.buttons.filter((b) => !b.includes("submit")).slice(0, 1);
319
- for (const btnSelector of nonFormButtons) {
320
- if (scenarios.length >= limit)
321
- break;
322
- const steps = [];
323
- if (page.path !== "/") {
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
- switch (tier) {
41
- case "pro":
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 (tier !== "free" && !hasPrimaryAction) {
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 (tier === "team" && scenarios.length < 4) {
99
- gaps.push("Too few meaningful scenarios were detected for a release-confidence pass, so this run stayed shallower than Team expects.");
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 (tier !== "free" && fillTarget && clickTarget && (requiredFillTarget || feedbackTarget)) {
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 (tier !== "free" && localLinkTarget) {
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 (tier === "team" && clickTarget && fillTarget && clickTarget !== fillTarget) {
166
+ if (clickTarget && fillTarget && clickTarget !== fillTarget) {
172
167
  scenarios.push({
173
168
  name: "Repeated interaction stability",
174
169
  steps: [
@@ -5,11 +5,11 @@ exports.getEntitlements = getEntitlements;
5
5
  exports.applyPlanOverride = applyPlanOverride;
6
6
  exports.printPlanBanner = printPlanBanner;
7
7
  /**
8
- * Fetches paid plan entitlements for laxy-verify.
8
+ * Fetches account entitlements for laxy-verify.
9
9
  *
10
- * The CLI asks /api/v1/cli-entitlement for the current plan and enabled features.
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 Free.
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 paid entitlements.");
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
  }
@@ -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 isReleaseTier
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 isReleaseTier
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 isReleaseTier
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 isReleaseTier
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 isReleaseTier
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 view.tier === "team"
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 a deeper Pro verification before sending this to a client."];
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 paid checks cover real interactions.",
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
- showDetailedLighthouse: false,
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
- showDetailedLighthouse: true,
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
- showDetailedLighthouse: true,
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
- switch (tier) {
45
- case "pro":
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
- "name": "laxy-verify",
3
- "version": "1.1.29",
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": "\u003e=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
- }
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
+ }