laxy-verify 1.1.7 → 1.1.8

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
@@ -3,10 +3,12 @@
3
3
  Frontend quality gate for AI-generated code. Build + Lighthouse verification with grade output (Gold/Silver/Bronze/Unverified).
4
4
 
5
5
  ```bash
6
- npx laxy-verify --init # Auto-detect framework, generate config
7
- npx laxy-verify . # Run verification
8
- npx laxy-verify login # Log in for Pro/Pro+ features
9
- npx laxy-verify --badge # Show shields.io badge
6
+ npx laxy-verify --init --run # 설정 생성 + 즉시 검증 (원커맨드)
7
+ npx laxy-verify --init # Auto-detect framework, generate config
8
+ npx laxy-verify . # Run verification
9
+ npx laxy-verify login # Log in for Pro/Pro+ features
10
+ npx laxy-verify --badge # Show shields.io badge
11
+ npx laxy-verify --help # 도움말 보기
10
12
  ```
11
13
 
12
14
  ## Quick Start
@@ -82,6 +84,14 @@ env:
82
84
  LAXY_TOKEN: ${{ secrets.LAXY_TOKEN }}
83
85
  ```
84
86
 
87
+ **LAXY_TOKEN을 GitHub Secrets에 등록하는 방법:**
88
+
89
+ 1. 로컬에서 로그인: `npx laxy-verify login`
90
+ 2. 토큰 복사: `cat ~/.laxy/credentials.json` → `"token"` 값을 복사
91
+ 3. GitHub 리포 → Settings → Secrets and variables → Actions → **New repository secret**
92
+ 4. Name: `LAXY_TOKEN`, Value: 복사한 토큰 붙여넣기
93
+ 5. `--init` 으로 생성된 workflow 파일은 이미 `LAXY_TOKEN`을 포함합니다
94
+
85
95
  ### Multi-viewport (Pro+)
86
96
 
87
97
  ```bash
package/dist/build.d.ts CHANGED
@@ -8,4 +8,4 @@ export declare class BuildError extends Error {
8
8
  timedOut: boolean;
9
9
  constructor(message: string, errors: string[], timedOut?: boolean);
10
10
  }
11
- export declare function runBuild(command: string, timeoutSec: number): Promise<BuildResult>;
11
+ export declare function runBuild(command: string, timeoutSec: number, cwd?: string): Promise<BuildResult>;
package/dist/build.js CHANGED
@@ -18,13 +18,17 @@ class BuildError extends Error {
18
18
  }
19
19
  }
20
20
  exports.BuildError = BuildError;
21
- function runBuild(command, timeoutSec) {
21
+ function runBuild(command, timeoutSec, cwd) {
22
22
  return new Promise((resolve, reject) => {
23
23
  const startTime = Date.now();
24
24
  const stderrLines = [];
25
25
  const errorLines = [];
26
- console.log(`\n Building: ${command}`);
27
- const proc = (0, node_child_process_1.spawn)(command, { shell: true, stdio: ["ignore", "pipe", "pipe"] });
26
+ console.log(`\n Building: ${command}${cwd ? ` (cwd: ${cwd})` : ""}`);
27
+ const proc = (0, node_child_process_1.spawn)(command, {
28
+ shell: true,
29
+ stdio: ["ignore", "pipe", "pipe"],
30
+ cwd,
31
+ });
28
32
  let timedOut = false;
29
33
  const timer = setTimeout(() => {
30
34
  timedOut = true;
package/dist/cli.js CHANGED
@@ -33,6 +33,9 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  return result;
34
34
  };
35
35
  })();
36
+ var __importDefault = (this && this.__importDefault) || function (mod) {
37
+ return (mod && mod.__esModule) ? mod : { "default": mod };
38
+ };
36
39
  Object.defineProperty(exports, "__esModule", { value: true });
37
40
  const fs = __importStar(require("node:fs"));
38
41
  const path = __importStar(require("node:path"));
@@ -49,6 +52,21 @@ const status_js_1 = require("./status.js");
49
52
  const auth_js_1 = require("./auth.js");
50
53
  const entitlement_js_1 = require("./entitlement.js");
51
54
  const multi_viewport_js_1 = require("./multi-viewport.js");
55
+ const index_js_1 = require("./verification-core/index.js");
56
+ const package_json_1 = __importDefault(require("../package.json"));
57
+ function exitGracefully(code) {
58
+ if (process.platform === "win32") {
59
+ setTimeout(() => process.exit(code), 100);
60
+ return;
61
+ }
62
+ process.exit(code);
63
+ }
64
+ async function ensurePortAvailableForVerification(port) {
65
+ const status = await (0, serve_js_1.probeServerStatus)(port);
66
+ if (status === null)
67
+ return;
68
+ throw new Error(`An existing local server is already responding on port ${port} (HTTP ${status}). Stop the running app before using laxy-verify, because the verification build can invalidate an active dev session.`);
69
+ }
52
70
  function parseArgs() {
53
71
  const raw = process.argv.slice(2);
54
72
  // Single-pass: collect flags and first positional
@@ -80,20 +98,20 @@ function parseArgs() {
80
98
  }
81
99
  }
82
100
  }
83
- // 서브커맨드 처리: login [email], logout, whoami
101
+ // ?쒕툕而ㅻ㎤??泥섎━: login [email], logout, whoami
84
102
  let subcommand;
85
103
  let subcommandArg;
86
104
  if (projectDir === "login" || projectDir === "logout" || projectDir === "whoami") {
87
105
  subcommand = projectDir;
88
106
  projectDir = ".";
89
- // login 뒤에 이메일이 올 수 있음
107
+ // login ?ㅼ뿉 ?대찓?쇱씠 ?????덉쓬
90
108
  if (subcommand === "login" && raw.length > 0 && !raw[0].startsWith("-")) {
91
109
  // already consumed via loop above if it was first; re-check flags
92
110
  }
93
- // 번째 non-flag 이메일로 취급
111
+ // 泥?踰덉㎏ non-flag瑜??대찓?쇰줈 痍④툒
94
112
  subcommandArg = flags["email"];
95
113
  if (!subcommandArg) {
96
- // login PSM@example.com 형태 positional already captured in projectDir before reset
114
+ // login PSM@example.com ?뺥깭 ??positional already captured in projectDir before reset
97
115
  }
98
116
  }
99
117
  return {
@@ -107,29 +125,59 @@ function parseArgs() {
107
125
  skipLighthouse: flags["skip-lighthouse"] !== undefined,
108
126
  badge: flags["badge"] !== undefined,
109
127
  init: flags["init"] !== undefined,
128
+ initRun: flags["init"] !== undefined && flags["run"] !== undefined,
110
129
  multiViewport: flags["multi-viewport"] !== undefined,
111
130
  failureAnalysis: flags["failure-analysis"] !== undefined,
131
+ help: flags["help"] !== undefined || flags["h"] !== undefined,
112
132
  };
113
133
  }
114
134
  function writeResultFile(projectDir, result) {
115
135
  const filePath = path.join(projectDir, ".laxy-result.json");
116
136
  fs.writeFileSync(filePath, JSON.stringify(result, null, 2) + "\n", "utf-8");
117
137
  }
138
+ function summarizeViewportIssues(scores, thresholds) {
139
+ if (!scores)
140
+ return { count: 0 };
141
+ const failed = [];
142
+ for (const [label, viewportScores] of Object.entries(scores)) {
143
+ if (!viewportScores) {
144
+ failed.push(`${label}: missing`);
145
+ continue;
146
+ }
147
+ const passes = viewportScores.performance >= thresholds.performance &&
148
+ viewportScores.accessibility >= thresholds.accessibility &&
149
+ viewportScores.seo >= thresholds.seo &&
150
+ viewportScores.bestPractices >= thresholds.bestPractices;
151
+ if (!passes) {
152
+ failed.push(`${label}: P${viewportScores.performance} A${viewportScores.accessibility} SEO${viewportScores.seo} BP${viewportScores.bestPractices}`);
153
+ }
154
+ }
155
+ return {
156
+ count: failed.length,
157
+ summary: failed.length > 0 ? failed.join(" | ") : "All checked viewports passed.",
158
+ };
159
+ }
118
160
  function consoleOutput(result) {
119
161
  const gradeLabel = result.grade;
120
- const checkEmoji = result.grade !== "Unverified" ? " " : "";
121
- console.log(`\n Laxy Verify ${gradeLabel}${checkEmoji}`);
162
+ const checkEmoji = result.grade !== "Unverified" ? " OK" : "";
163
+ console.log(`\n Laxy Verify ${gradeLabel}${checkEmoji}`);
122
164
  console.log(` Build: ${result.build.success ? `OK (${result.build.durationMs}ms)` : "FAILED"}`);
123
165
  if (result.build.errors.length > 0) {
124
- const last5 = result.build.errors.slice(-5);
125
166
  console.log(` Errors:`);
126
- for (const e of last5)
167
+ // 泥?踰덉㎏ 'error' ?ㅼ썙?쒓? ?ы븿??以?(?먯씤 ?뚯븙??
168
+ const firstError = result.build.errors.find(e => /error/i.test(e));
169
+ // 留덉?留?5以?(鍮뚮뱶 醫낅즺 而⑦뀓?ㅽ듃)
170
+ const last5 = result.build.errors.slice(-5);
171
+ const toShow = firstError && !last5.includes(firstError)
172
+ ? [firstError, " ...", ...last5]
173
+ : last5;
174
+ for (const e of toShow)
127
175
  console.error(` ${e}`);
128
176
  }
129
177
  if (result.lighthouse !== null) {
130
178
  const lh = result.lighthouse;
131
179
  const t = result.thresholds;
132
- const check = (passed) => passed ? " " : " ";
180
+ const check = (passed) => (passed ? " OK" : " FAIL");
133
181
  console.log(` Lighthouse:`);
134
182
  console.log(` Performance: ${lh.performance} / ${t.performance}${check(lh.performance >= t.performance)}`);
135
183
  console.log(` Accessibility: ${lh.accessibility} / ${t.accessibility}${check(lh.accessibility >= t.accessibility)}`);
@@ -140,6 +188,28 @@ function consoleOutput(result) {
140
188
  else {
141
189
  console.log(` Lighthouse: skipped`);
142
190
  }
191
+ if (result.verification) {
192
+ const view = result.verification.view;
193
+ console.log(` Verification tier: ${view.tier}`);
194
+ console.log(` Question: ${view.question}`);
195
+ console.log(` Verdict: ${view.verdict} (${view.confidence})`);
196
+ console.log(` Summary: ${view.summary}`);
197
+ if (view.blockers.length > 0) {
198
+ console.log(` Blockers:`);
199
+ for (const blocker of view.blockers)
200
+ console.log(` - ${blocker.title}`);
201
+ }
202
+ if (view.nextActions.length > 0) {
203
+ console.log(` Next actions:`);
204
+ for (const action of view.nextActions)
205
+ console.log(` - ${action}`);
206
+ }
207
+ if (view.failureEvidence.length > 0) {
208
+ console.log(` Evidence:`);
209
+ for (const item of view.failureEvidence)
210
+ console.log(` - ${item}`);
211
+ }
212
+ }
143
213
  if (result.github) {
144
214
  if (result.github.status === "comment_posted")
145
215
  console.log(` PR comment: posted`);
@@ -148,45 +218,100 @@ function consoleOutput(result) {
148
218
  }
149
219
  console.log(` Result: .laxy-result.json`);
150
220
  console.log(` Exit code: ${result.exitCode}`);
221
+ // Upsell CTA: Gold ?ъ꽦?????먭퀬 Free ?뚮옖????
222
+ if (result.grade === "silver" || result.grade === "bronze") {
223
+ if (!result._plan || result._plan === "free") {
224
+ console.log(`\n Gold ?깃툒???먰븯?좊떎硫?Pro/Pro+濡??낃렇?덉씠?쒗븯?몄슂:`);
225
+ console.log(` ??https://laxy-blue.vercel.app/pricing`);
226
+ }
227
+ }
151
228
  }
152
229
  async function run() {
153
230
  const args = parseArgs();
154
- // ── 서브커맨드 처리 ──────────────────────────────────────────────────────
231
+ // ?€?€ --help ?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€
232
+ if (args.help) {
233
+ console.log(`
234
+ laxy-verify v${package_json_1.default.version}
235
+ Frontend quality gate: build + Lighthouse verification
236
+
237
+ Usage:
238
+ npx laxy-verify [project-dir] [options]
239
+ npx laxy-verify <subcommand>
240
+
241
+ Subcommands:
242
+ login [email] Log in to unlock Pro/Pro+ features
243
+ logout Remove saved credentials
244
+ whoami Show current login status
245
+
246
+ Options:
247
+ --init Generate .laxy.yml + GitHub workflow file
248
+ --init --run Generate config and immediately run verification
249
+ --format console | json (default: console)
250
+ --ci CI mode: -10 Performance threshold, runs=3
251
+ --config <path> Path to .laxy.yml
252
+ --fail-on unverified | bronze | silver | gold
253
+ --skip-lighthouse Build-only (max Bronze grade)
254
+ --multi-viewport Pro+: Lighthouse on desktop/tablet/mobile
255
+ --badge Print shields.io badge markdown
256
+ --help Show this help
257
+
258
+ Exit codes:
259
+ 0 Grade meets or exceeds fail_on threshold
260
+ 1 Grade worse than fail_on, or build failed
261
+ 2 Configuration error
262
+
263
+ Examples:
264
+ npx laxy-verify --init --run # Setup + first verification
265
+ npx laxy-verify . # Run in current directory
266
+ npx laxy-verify . --ci # CI mode
267
+ npx laxy-verify . --fail-on silver # Require Silver or better
268
+
269
+ Docs: https://github.com/psungmin24/laxy-verify
270
+ `);
271
+ exitGracefully(0);
272
+ return;
273
+ }
274
+ // ?€?€ ?쒕툕而ㅻ㎤??泥섎━ ?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€
155
275
  if (args.subcommand === "login") {
156
276
  await (0, auth_js_1.login)(args.subcommandArg);
157
- // Windows: fetch()TCP 소켓이 closing 중에 process.exit() 즉시 호출하면
158
- // UV_HANDLE_CLOSING Assertion 발생. setTimeout으로 UV 루프에 정리 시간을 준다.
159
- setTimeout(() => process.exit(0), 100);
277
+ // Windows: fetch()??TCP ?뚯폆??closing 以묒뿉 process.exit()??利됱떆 ?몄텧?섎㈃
278
+ // UV_HANDLE_CLOSING Assertion 諛쒖깮. setTimeout?쇰줈 UV 猷⑦봽???뺣━ ?쒓컙??以€??
279
+ exitGracefully(0);
160
280
  return;
161
281
  }
162
282
  if (args.subcommand === "logout") {
163
283
  (0, auth_js_1.clearToken)();
164
- process.exit(0);
284
+ exitGracefully(0);
165
285
  return;
166
286
  }
167
287
  if (args.subcommand === "whoami") {
168
288
  (0, auth_js_1.whoami)();
169
- process.exit(0);
289
+ exitGracefully(0);
170
290
  return;
171
291
  }
172
292
  // --init
173
293
  if (args.init) {
174
294
  (0, init_js_1.runInit)(args.projectDir);
175
- process.exit(0);
176
- return;
295
+ if (!args.initRun) {
296
+ console.log(`\n ?ㅼ쓬 ?④퀎: npx laxy-verify . (?먮뒗 --init --run ?쇰줈 諛붾줈 ?ㅽ뻾)`);
297
+ exitGracefully(0);
298
+ return;
299
+ }
300
+ console.log("\n 泥?踰덉㎏ 寃€利앹쓣 ?쒖옉?⑸땲??..\n");
301
+ // initRun: fall through to run verification below
177
302
  }
178
303
  // --badge
179
304
  if (args.badge) {
180
305
  const resultPath = path.join(args.projectDir, ".laxy-result.json");
181
306
  if (!fs.existsSync(resultPath)) {
182
307
  console.error("Error: .laxy-result.json not found. Run `npx laxy-verify .` first to generate it.");
183
- process.exit(2);
308
+ exitGracefully(2);
184
309
  return;
185
310
  }
186
311
  const content = JSON.parse(fs.readFileSync(resultPath, "utf-8"));
187
312
  const badge = (0, badge_js_1.generateBadge)(content.grade);
188
313
  console.log(badge);
189
- process.exit(0);
314
+ exitGracefully(0);
190
315
  return;
191
316
  }
192
317
  // Load config
@@ -204,7 +329,7 @@ async function run() {
204
329
  }
205
330
  catch (err) {
206
331
  console.error(`Config error: ${err instanceof Error ? err.message : String(err)}`);
207
- process.exit(2);
332
+ exitGracefully(2);
208
333
  return;
209
334
  }
210
335
  // Auto-detect framework + package manager
@@ -214,17 +339,25 @@ async function run() {
214
339
  }
215
340
  catch (err) {
216
341
  console.error(`Detection error: ${err instanceof Error ? err.message : String(err)}`);
217
- process.exit(2);
342
+ exitGracefully(2);
218
343
  return;
219
344
  }
220
345
  // Merge config overrides
221
346
  const buildCmd = config.build_command || detected.buildCmd;
222
347
  const devCmd = config.dev_command || detected.devCmd;
223
348
  const port = config.port;
349
+ try {
350
+ await ensurePortAvailableForVerification(port);
351
+ }
352
+ catch (err) {
353
+ console.error(`Preflight error: ${err instanceof Error ? err.message : String(err)}`);
354
+ exitGracefully(2);
355
+ return;
356
+ }
224
357
  // Phase 1: Build
225
358
  let buildResult;
226
359
  try {
227
- buildResult = await (0, build_js_1.runBuild)(buildCmd, config.build_timeout);
360
+ buildResult = await (0, build_js_1.runBuild)(buildCmd, config.build_timeout, args.projectDir);
228
361
  }
229
362
  catch (err) {
230
363
  buildResult = {
@@ -243,14 +376,14 @@ async function run() {
243
376
  seo: config.thresholds.seo,
244
377
  bestPractices: config.thresholds.bestPractices,
245
378
  };
246
- // ── 플랜 기능 조회 (토큰 없으면 Free 폴백) ──────────────────────────────
379
+ // ?€?€ ?뚮옖 湲곕뒫 議고쉶 (?좏겙 ?놁쑝硫?Free ?대갚) ?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€
247
380
  let entitlements = null;
248
381
  try {
249
382
  entitlements = await (0, entitlement_js_1.getEntitlements)();
250
383
  (0, entitlement_js_1.printPlanBanner)(entitlements);
251
384
  }
252
385
  catch {
253
- // 네트워크 오류 Free로 진행
386
+ // ?ㅽ듃?뚰겕 ?ㅻ쪟 ??Free濡?吏꾪뻾
254
387
  }
255
388
  const features = entitlements ?? {
256
389
  plan: "free",
@@ -261,7 +394,7 @@ async function run() {
261
394
  failure_analysis: false,
262
395
  fast_lane: false,
263
396
  };
264
- // Pro 이상이면 Lighthouse 3 실행 (더 정밀)
397
+ // Pro ?댁긽?대㈃ Lighthouse 3???ㅽ뻾 (???뺣?)
265
398
  if (features.lighthouse_runs_3 && config.lighthouse_runs < 3) {
266
399
  config = { ...config, lighthouse_runs: 3 };
267
400
  }
@@ -271,7 +404,7 @@ async function run() {
271
404
  if (buildResult.success && !args.skipLighthouse) {
272
405
  let servePid;
273
406
  try {
274
- const serve = await (0, serve_js_1.startDevServer)(devCmd, port, config.dev_timeout);
407
+ const serve = await (0, serve_js_1.startDevServer)(devCmd, port, config.dev_timeout, args.projectDir);
275
408
  servePid = serve.pid;
276
409
  try {
277
410
  const lhResult = await (0, lighthouse_js_1.runLighthouse)(port, config.lighthouse_runs);
@@ -289,9 +422,9 @@ async function run() {
289
422
  catch (lhErr) {
290
423
  console.error(`Lighthouse error: ${lhErr instanceof Error ? lhErr.message : String(lhErr)}`);
291
424
  }
292
- // ── Pro+: 멀티 뷰포트 (dev server 돌리는 중) ─────────────────────
425
+ // ?€?€ Pro+: 硫€??酉고룷??(dev server媛€ ?뚮━??以? ?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€?€
293
426
  if (args.multiViewport && !features.multi_viewport) {
294
- console.log("\n ⚠️ --multi-viewportPro+ 플랜 전용입니다. laxy-verify login 으로 로그인하세요.");
427
+ console.log("\n ?좑툘 --multi-viewport??Pro+ ?뚮옖 ?꾩슜?낅땲?? laxy-verify login ?쇰줈 濡쒓렇?명븯?몄슂.");
295
428
  }
296
429
  else if (features.multi_viewport) {
297
430
  try {
@@ -321,6 +454,27 @@ async function run() {
321
454
  failOn: config.fail_on,
322
455
  goldEligible: features.gold_grade && allViewportsOk,
323
456
  });
457
+ const verificationTier = (0, index_js_1.planToVerificationTier)(features.plan);
458
+ const viewportSummary = summarizeViewportIssues(multiViewportScores, adjustedThresholds);
459
+ const failureEvidence = [
460
+ ...buildResult.errors.slice(0, 3).map((error) => `Build: ${error}`),
461
+ ...(viewportSummary.count > 0 && viewportSummary.summary
462
+ ? [`Viewport: ${viewportSummary.summary}`]
463
+ : []),
464
+ ];
465
+ const verificationReport = (0, index_js_1.buildVerificationReport)({
466
+ buildSuccess: buildResult.success,
467
+ buildErrors: buildResult.errors,
468
+ viewportIssues: multiViewportScores ? viewportSummary.count : undefined,
469
+ multiViewportPassed: multiViewportScores ? allViewportsOk : undefined,
470
+ multiViewportSummary: multiViewportScores ? viewportSummary.summary : undefined,
471
+ lighthouseScores: scores,
472
+ failureEvidence,
473
+ }, {
474
+ tier: verificationTier,
475
+ thresholds: adjustedThresholds,
476
+ });
477
+ const verificationView = (0, index_js_1.getTierVerificationView)(verificationReport);
324
478
  // Build result object
325
479
  const resultObj = {
326
480
  grade: gradeResult.grade.charAt(0).toUpperCase() + gradeResult.grade.slice(1), // Capitalize
@@ -336,6 +490,12 @@ async function run() {
336
490
  framework: detected.framework,
337
491
  exitCode: gradeResult.exitCode,
338
492
  config_fail_on: config.fail_on,
493
+ _plan: features.plan,
494
+ verification: {
495
+ tier: verificationTier,
496
+ report: verificationReport,
497
+ view: verificationView,
498
+ },
339
499
  };
340
500
  // GitHub integration (only in Actions)
341
501
  const inGitHubActions = !!process.env.GITHUB_ACTIONS;
@@ -364,9 +524,9 @@ async function run() {
364
524
  if (inGitHubActions && process.env.GITHUB_OUTPUT) {
365
525
  fs.appendFileSync(process.env.GITHUB_OUTPUT, `grade=${resultObj.grade}\n`);
366
526
  }
367
- process.exit(gradeResult.exitCode);
527
+ exitGracefully(gradeResult.exitCode);
368
528
  }
369
529
  run().catch((err) => {
370
530
  console.error(`Fatal error: ${err instanceof Error ? err.message : String(err)}`);
371
- process.exit(1);
531
+ exitGracefully(1);
372
532
  });
package/dist/serve.d.ts CHANGED
@@ -8,5 +8,6 @@ export interface ServeResult {
8
8
  pid: number;
9
9
  port: number;
10
10
  }
11
- export declare function startDevServer(command: string, port: number, timeoutSec: number): Promise<ServeResult>;
11
+ export declare function probeServerStatus(port: number): Promise<number | null>;
12
+ export declare function startDevServer(command: string, port: number, timeoutSec: number, cwd?: string): Promise<ServeResult>;
12
13
  export declare function stopDevServer(pid: number): void;
package/dist/serve.js CHANGED
@@ -37,6 +37,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.DevServerTimeoutError = exports.PortConflictError = void 0;
40
+ exports.probeServerStatus = probeServerStatus;
40
41
  exports.startDevServer = startDevServer;
41
42
  exports.stopDevServer = stopDevServer;
42
43
  const node_child_process_1 = require("node:child_process");
@@ -67,13 +68,17 @@ function httpGet(url) {
67
68
  });
68
69
  });
69
70
  }
70
- async function startDevServer(command, port, timeoutSec) {
71
+ function probeServerStatus(port) {
72
+ return httpGet(`http://localhost:${port}/`);
73
+ }
74
+ async function startDevServer(command, port, timeoutSec, cwd) {
71
75
  return new Promise((resolve, reject) => {
72
- console.log(`Starting dev server: ${command}`);
76
+ console.log(`Starting dev server: ${command}${cwd ? ` (cwd: ${cwd})` : ""}`);
73
77
  const proc = (0, node_child_process_1.spawn)(command, {
74
78
  shell: true,
75
79
  stdio: ["ignore", "pipe", "pipe"],
76
80
  env: { ...process.env, PORT: String(port) },
81
+ cwd,
77
82
  });
78
83
  // Pipe output to console
79
84
  proc.stdout?.on("data", (chunk) => {
@@ -103,7 +108,7 @@ async function startDevServer(command, port, timeoutSec) {
103
108
  reject(new DevServerTimeoutError(port, timeoutSec));
104
109
  return;
105
110
  }
106
- const status = await httpGet(`http://localhost:${port}/`);
111
+ const status = await probeServerStatus(port);
107
112
  if (status !== null) {
108
113
  if (status === 200) {
109
114
  console.log(`Dev server ready on port ${port} (HTTP ${status})`);
@@ -0,0 +1,3 @@
1
+ export * from "./types.js";
2
+ export * from "./tier-policy.js";
3
+ export * from "./report.js";
@@ -0,0 +1,19 @@
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types.js"), exports);
18
+ __exportStar(require("./tier-policy.js"), exports);
19
+ __exportStar(require("./report.js"), exports);
@@ -0,0 +1,14 @@
1
+ import type { LighthouseThresholds, TierVerificationView, VerificationEvidence, VerificationFinding, VerificationGrade, VerificationInput, VerificationReport, VerificationTier } from "./types.js";
2
+ export declare const DEFAULT_LH_THRESHOLDS: LighthouseThresholds;
3
+ export declare function getLighthousePass(lighthouseScores?: VerificationInput["lighthouseScores"], thresholds?: LighthouseThresholds): boolean;
4
+ export declare function getVerificationGrade(input: VerificationInput, thresholds?: LighthouseThresholds): VerificationGrade;
5
+ export declare function buildVerificationEvidence(input: VerificationInput, thresholds?: LighthouseThresholds): VerificationEvidence;
6
+ export declare function getImprovementRecommendations(input: VerificationInput, thresholds?: LighthouseThresholds): VerificationFinding[];
7
+ export declare function buildVerificationReport(input: VerificationInput, options?: {
8
+ tier?: VerificationTier;
9
+ thresholds?: LighthouseThresholds;
10
+ }): VerificationReport;
11
+ export declare function buildTierVerificationView(input: VerificationInput, options?: {
12
+ tier?: VerificationTier;
13
+ thresholds?: LighthouseThresholds;
14
+ }): TierVerificationView;
@@ -0,0 +1,273 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_LH_THRESHOLDS = void 0;
4
+ exports.getLighthousePass = getLighthousePass;
5
+ exports.getVerificationGrade = getVerificationGrade;
6
+ exports.buildVerificationEvidence = buildVerificationEvidence;
7
+ exports.getImprovementRecommendations = getImprovementRecommendations;
8
+ exports.buildVerificationReport = buildVerificationReport;
9
+ exports.buildTierVerificationView = buildTierVerificationView;
10
+ const tier_policy_js_1 = require("./tier-policy.js");
11
+ exports.DEFAULT_LH_THRESHOLDS = {
12
+ performance: 70,
13
+ accessibility: 85,
14
+ seo: 80,
15
+ bestPractices: 80,
16
+ };
17
+ function getLighthousePass(lighthouseScores, thresholds = exports.DEFAULT_LH_THRESHOLDS) {
18
+ if (!lighthouseScores)
19
+ return false;
20
+ return (lighthouseScores.performance >= thresholds.performance &&
21
+ lighthouseScores.accessibility >= thresholds.accessibility &&
22
+ lighthouseScores.seo >= thresholds.seo &&
23
+ lighthouseScores.bestPractices >= thresholds.bestPractices);
24
+ }
25
+ function getVerificationGrade(input, thresholds = exports.DEFAULT_LH_THRESHOLDS) {
26
+ const buildPassed = input.buildSuccess === true;
27
+ const e2ePassedAll = typeof input.e2ePassed === "number" &&
28
+ typeof input.e2eTotal === "number" &&
29
+ input.e2eTotal > 0 &&
30
+ input.e2ePassed === input.e2eTotal;
31
+ const lighthousePassed = getLighthousePass(input.lighthouseScores, thresholds);
32
+ if (buildPassed && e2ePassedAll && lighthousePassed)
33
+ return "gold";
34
+ if (buildPassed && e2ePassedAll)
35
+ return "silver";
36
+ if (buildPassed)
37
+ return "bronze";
38
+ return "unverified";
39
+ }
40
+ function buildVerificationEvidence(input, thresholds = exports.DEFAULT_LH_THRESHOLDS) {
41
+ const buildPassed = input.buildSuccess === true;
42
+ const hasE2EData = typeof input.e2eTotal === "number" && input.e2eTotal > 0;
43
+ const e2ePassedAll = hasE2EData &&
44
+ typeof input.e2ePassed === "number" &&
45
+ typeof input.e2eTotal === "number" &&
46
+ input.e2ePassed === input.e2eTotal;
47
+ const hasMultiViewportData = typeof input.viewportIssues === "number" || typeof input.multiViewportPassed === "boolean";
48
+ const multiViewportPassed = hasMultiViewportData
49
+ ? input.multiViewportPassed === true ||
50
+ (input.multiViewportPassed !== false && (input.viewportIssues ?? 0) <= 0)
51
+ : false;
52
+ const hasVisualDiffData = typeof input.visualDiffVerdict === "string";
53
+ const visualDiffPassed = hasVisualDiffData &&
54
+ input.visualDiffVerdict !== "warn" &&
55
+ input.visualDiffVerdict !== "rollback";
56
+ const lighthousePassed = getLighthousePass(input.lighthouseScores, thresholds);
57
+ return {
58
+ input,
59
+ thresholds,
60
+ buildPassed,
61
+ hasE2EData,
62
+ e2ePassedAll,
63
+ hasMultiViewportData,
64
+ multiViewportPassed,
65
+ hasVisualDiffData,
66
+ visualDiffPassed,
67
+ lighthousePassed,
68
+ };
69
+ }
70
+ function getImprovementRecommendations(input, thresholds = exports.DEFAULT_LH_THRESHOLDS) {
71
+ const findings = [];
72
+ const errors = input.buildErrors ?? [];
73
+ if (input.buildSuccess === false) {
74
+ if (errors.some((error) => /TS\d+|type/i.test(error))) {
75
+ findings.push({
76
+ category: "build",
77
+ severity: "critical",
78
+ title: "TypeScript build errors",
79
+ description: "Type errors are blocking a clean production build.",
80
+ action: "Fix the TypeScript errors first, then rerun verification.",
81
+ });
82
+ }
83
+ if (errors.some((error) => /Module not found|Cannot find module|Failed to resolve/i.test(error))) {
84
+ findings.push({
85
+ category: "build",
86
+ severity: "critical",
87
+ title: "Missing or unresolved modules",
88
+ description: "The build cannot resolve one or more imports or packages.",
89
+ action: "Check import paths, package installation, and package.json consistency.",
90
+ });
91
+ }
92
+ if (errors.some((error) => /SyntaxError|Unexpected token/i.test(error))) {
93
+ findings.push({
94
+ category: "build",
95
+ severity: "critical",
96
+ title: "Syntax errors in source code",
97
+ description: "The code contains syntax issues that stop the build from completing.",
98
+ action: "Fix the syntax errors, then rerun the build verification.",
99
+ });
100
+ }
101
+ if (findings.every((finding) => finding.category !== "build")) {
102
+ findings.push({
103
+ category: "build",
104
+ severity: "critical",
105
+ title: "Build failed",
106
+ description: "Production build verification did not pass.",
107
+ action: "Inspect the build logs and resolve the blocking errors before release.",
108
+ });
109
+ }
110
+ }
111
+ if (typeof input.e2ePassed === "number" &&
112
+ typeof input.e2eTotal === "number" &&
113
+ input.e2eTotal > 0 &&
114
+ input.e2ePassed < input.e2eTotal) {
115
+ findings.push({
116
+ category: "e2e",
117
+ severity: "high",
118
+ title: `E2E failures (${input.e2ePassed}/${input.e2eTotal})`,
119
+ description: "One or more verification scenarios failed.",
120
+ action: "Fix the broken user flow and rerun the verification scenarios.",
121
+ });
122
+ }
123
+ const hasMultiViewportData = typeof input.viewportIssues === "number" || typeof input.multiViewportPassed === "boolean";
124
+ const multiViewportPassed = input.multiViewportPassed === true ||
125
+ (input.multiViewportPassed !== false && (input.viewportIssues ?? 0) <= 0);
126
+ if (hasMultiViewportData && !multiViewportPassed) {
127
+ findings.push({
128
+ category: "viewport",
129
+ severity: "high",
130
+ title: `Multi-viewport issues detected (${input.viewportIssues ?? 0})`,
131
+ description: input.multiViewportSummary ||
132
+ "One or more responsive layout or viewport-specific verification issues were found.",
133
+ action: "Fix the responsive layout issues and rerun the multi-viewport verification pass.",
134
+ });
135
+ }
136
+ if (input.visualDiffVerdict === "rollback") {
137
+ findings.push({
138
+ category: "visual",
139
+ severity: "high",
140
+ title: `Visual regression detected (${input.visualDiffPercentage ?? 0}%)`,
141
+ description: "The visual diff is large enough to recommend a rollback or release hold.",
142
+ action: "Review the visual diff artifacts and fix the unintended UI regression before release.",
143
+ });
144
+ }
145
+ else if (input.visualDiffVerdict === "warn") {
146
+ findings.push({
147
+ category: "visual",
148
+ severity: "medium",
149
+ title: `Visual change needs review (${input.visualDiffPercentage ?? 0}%)`,
150
+ description: "The visual diff changed enough to require a manual review before release.",
151
+ action: "Check the visual diff and confirm the UI change is intentional.",
152
+ });
153
+ }
154
+ const lighthouseScores = input.lighthouseScores;
155
+ if (!lighthouseScores) {
156
+ return findings;
157
+ }
158
+ const lighthouseFinding = (category, actual, required, title, description, action) => ({
159
+ category,
160
+ severity: required - actual >= 20 ? "high" : "medium",
161
+ title: `${title} (${actual} / ${required})`,
162
+ description,
163
+ action,
164
+ });
165
+ if (lighthouseScores.performance < thresholds.performance) {
166
+ findings.push(lighthouseFinding("performance", lighthouseScores.performance, thresholds.performance, "Performance below threshold", "Runtime performance is below the minimum verification threshold.", "Reduce heavy assets, expensive scripts, and blocking work on initial load."));
167
+ }
168
+ if (lighthouseScores.accessibility < thresholds.accessibility) {
169
+ findings.push(lighthouseFinding("accessibility", lighthouseScores.accessibility, thresholds.accessibility, "Accessibility below threshold", "Accessibility checks are below the minimum verification threshold.", "Fix labels, semantics, contrast, and keyboard accessibility issues."));
170
+ }
171
+ if (lighthouseScores.seo < thresholds.seo) {
172
+ findings.push(lighthouseFinding("seo", lighthouseScores.seo, thresholds.seo, "SEO below threshold", "SEO checks are below the minimum verification threshold.", "Fix title, description, crawl settings, and indexable metadata."));
173
+ }
174
+ if (lighthouseScores.bestPractices < thresholds.bestPractices) {
175
+ findings.push(lighthouseFinding("bestPractices", lighthouseScores.bestPractices, thresholds.bestPractices, "Best practices below threshold", "Best practices checks are below the minimum verification threshold.", "Fix browser warnings, unsafe patterns, and platform-level issues."));
176
+ }
177
+ return findings.sort((a, b) => {
178
+ const priority = { critical: 0, high: 1, medium: 2 };
179
+ return priority[a.severity] - priority[b.severity];
180
+ });
181
+ }
182
+ function buildVerificationReport(input, options) {
183
+ const thresholds = options?.thresholds ?? exports.DEFAULT_LH_THRESHOLDS;
184
+ const tier = options?.tier ?? "free";
185
+ const evidence = buildVerificationEvidence(input, thresholds);
186
+ const findings = getImprovementRecommendations(input, thresholds);
187
+ const blockers = findings.filter((finding) => finding.severity === "critical" || finding.severity === "high");
188
+ const warnings = findings.filter((finding) => finding.severity === "medium");
189
+ const grade = getVerificationGrade(input, thresholds);
190
+ const failureEvidence = (input.failureEvidence ?? []).filter(Boolean).slice(0, 5);
191
+ let verdict;
192
+ let confidence;
193
+ let summary;
194
+ if (!evidence.buildPassed) {
195
+ verdict = "build-failed";
196
+ confidence = "low";
197
+ summary = "Build failed. Fix the blocking build errors before relying on this verification result.";
198
+ }
199
+ else if (blockers.length > 0) {
200
+ verdict = "hold";
201
+ confidence = tier === "free" ? "low" : "medium";
202
+ summary = "Blocking verification issues were found. Hold release until the blockers are fixed.";
203
+ }
204
+ else if (tier === "pro_plus" &&
205
+ evidence.buildPassed &&
206
+ evidence.e2ePassedAll &&
207
+ evidence.lighthousePassed &&
208
+ evidence.hasMultiViewportData &&
209
+ evidence.multiViewportPassed) {
210
+ verdict = "release-ready";
211
+ confidence = "high";
212
+ summary = "Core verification checks passed. This run supports a release-ready call.";
213
+ }
214
+ else if (tier === "pro_plus" &&
215
+ evidence.buildPassed &&
216
+ evidence.e2ePassedAll &&
217
+ evidence.lighthousePassed &&
218
+ !evidence.hasMultiViewportData) {
219
+ verdict = "investigate";
220
+ confidence = "medium";
221
+ summary = "Core checks passed, but release-ready confidence still needs multi-viewport verification evidence.";
222
+ }
223
+ else if (tier === "free") {
224
+ verdict = "quick-pass";
225
+ confidence = evidence.lighthousePassed ? "medium" : "low";
226
+ summary = "No immediate hard blockers were found in the quick verification pass.";
227
+ }
228
+ else {
229
+ verdict = "investigate";
230
+ confidence = "medium";
231
+ summary = "The build is standing, but deeper verification evidence should be reviewed before release.";
232
+ }
233
+ const passes = [
234
+ { key: "build", label: "Production build", passed: evidence.buildPassed },
235
+ ...(evidence.hasE2EData
236
+ ? [{ key: "e2e", label: `E2E ${input.e2ePassed ?? 0}/${input.e2eTotal ?? 0}`, passed: evidence.e2ePassedAll }]
237
+ : []),
238
+ ...(evidence.hasMultiViewportData
239
+ ? [{
240
+ key: "viewport",
241
+ label: `Viewport ${input.viewportIssues ?? 0} issues`,
242
+ passed: evidence.multiViewportPassed,
243
+ }]
244
+ : []),
245
+ ...(evidence.hasVisualDiffData
246
+ ? [{
247
+ key: "visual",
248
+ label: input.hasVisualBaseline
249
+ ? `Visual diff ${input.visualDiffPercentage ?? 0}%`
250
+ : "Visual baseline seeded",
251
+ passed: input.visualDiffVerdict !== "rollback",
252
+ }]
253
+ : []),
254
+ { key: "lighthouse", label: "Lighthouse thresholds", passed: evidence.lighthousePassed },
255
+ ];
256
+ const nextActions = [...blockers, ...warnings].slice(0, 4).map((finding) => finding.action);
257
+ return {
258
+ tier,
259
+ verdict,
260
+ confidence,
261
+ summary,
262
+ grade,
263
+ blockers,
264
+ warnings,
265
+ passes,
266
+ nextActions,
267
+ failureEvidence,
268
+ evidence,
269
+ };
270
+ }
271
+ function buildTierVerificationView(input, options) {
272
+ return (0, tier_policy_js_1.getTierVerificationView)(buildVerificationReport(input, options));
273
+ }
@@ -0,0 +1,13 @@
1
+ import type { TierVerificationView, VerificationReport, VerificationTier } from "./types.js";
2
+ export interface TierPolicy {
3
+ tier: VerificationTier;
4
+ showDetailedLighthouse: boolean;
5
+ showDetailedE2E: boolean;
6
+ showReportExport: boolean;
7
+ maxBlockers: number;
8
+ maxWarnings: number;
9
+ }
10
+ export declare function getTierPolicy(tier?: VerificationTier): TierPolicy;
11
+ export declare function planToVerificationTier(plan?: string | null): VerificationTier;
12
+ export declare function getVerificationTierQuestion(tier: VerificationTier): string;
13
+ export declare function getTierVerificationView(report: VerificationReport): TierVerificationView;
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getTierPolicy = getTierPolicy;
4
+ exports.planToVerificationTier = planToVerificationTier;
5
+ exports.getVerificationTierQuestion = getVerificationTierQuestion;
6
+ exports.getTierVerificationView = getTierVerificationView;
7
+ const TIER_POLICIES = {
8
+ free: {
9
+ tier: "free",
10
+ showDetailedLighthouse: false,
11
+ showDetailedE2E: false,
12
+ showReportExport: false,
13
+ maxBlockers: 1,
14
+ maxWarnings: 2,
15
+ },
16
+ pro: {
17
+ tier: "pro",
18
+ showDetailedLighthouse: true,
19
+ showDetailedE2E: true,
20
+ showReportExport: true,
21
+ maxBlockers: 5,
22
+ maxWarnings: 5,
23
+ },
24
+ pro_plus: {
25
+ tier: "pro_plus",
26
+ showDetailedLighthouse: true,
27
+ showDetailedE2E: true,
28
+ showReportExport: true,
29
+ maxBlockers: 8,
30
+ maxWarnings: 8,
31
+ },
32
+ };
33
+ function getTierPolicy(tier = "free") {
34
+ return TIER_POLICIES[tier];
35
+ }
36
+ function planToVerificationTier(plan) {
37
+ if (plan === "pro")
38
+ return "pro";
39
+ if (plan === "pro_plus" || plan === "team" || plan === "enterprise")
40
+ return "pro_plus";
41
+ return "free";
42
+ }
43
+ function getVerificationTierQuestion(tier) {
44
+ switch (tier) {
45
+ case "pro":
46
+ return "Is this strong enough to send to a client?";
47
+ case "pro_plus":
48
+ return "Can I call this release-ready with confidence?";
49
+ default:
50
+ return "Is this likely to break right now?";
51
+ }
52
+ }
53
+ function getTierVerificationView(report) {
54
+ const policy = getTierPolicy(report.tier);
55
+ return {
56
+ tier: report.tier,
57
+ question: getVerificationTierQuestion(report.tier),
58
+ verdict: report.verdict,
59
+ confidence: report.confidence,
60
+ summary: report.summary,
61
+ grade: report.grade,
62
+ blockers: report.blockers.slice(0, policy.maxBlockers),
63
+ warnings: report.warnings.slice(0, policy.maxWarnings),
64
+ passes: report.passes,
65
+ nextActions: report.nextActions.slice(0, Math.max(2, policy.maxWarnings)),
66
+ failureEvidence: report.failureEvidence.slice(0, Math.max(2, policy.maxWarnings)),
67
+ showDetailedLighthouse: policy.showDetailedLighthouse,
68
+ showDetailedE2E: policy.showDetailedE2E,
69
+ showReportExport: policy.showReportExport,
70
+ };
71
+ }
@@ -0,0 +1,82 @@
1
+ export type VerificationGrade = "gold" | "silver" | "bronze" | "unverified";
2
+ export type VerificationTier = "free" | "pro" | "pro_plus";
3
+ export type ReleaseVerdict = "quick-pass" | "investigate" | "hold" | "release-ready" | "build-failed";
4
+ export interface LighthouseThresholds {
5
+ performance: number;
6
+ accessibility: number;
7
+ seo: number;
8
+ bestPractices: number;
9
+ }
10
+ export interface LighthouseScores {
11
+ performance: number;
12
+ accessibility: number;
13
+ bestPractices: number;
14
+ seo: number;
15
+ }
16
+ export interface VerificationInput {
17
+ buildSuccess?: boolean;
18
+ buildErrors?: string[];
19
+ e2ePassed?: number;
20
+ e2eTotal?: number;
21
+ viewportIssues?: number;
22
+ multiViewportPassed?: boolean;
23
+ multiViewportSummary?: string;
24
+ visualDiffVerdict?: "pass" | "warn" | "rollback";
25
+ visualDiffPercentage?: number;
26
+ hasVisualBaseline?: boolean;
27
+ failureEvidence?: string[];
28
+ lighthouseScores?: LighthouseScores;
29
+ }
30
+ export interface VerificationCheck {
31
+ key: "build" | "e2e" | "lighthouse" | "viewport" | "visual";
32
+ label: string;
33
+ passed: boolean;
34
+ }
35
+ export interface VerificationFinding {
36
+ category: "build" | "performance" | "accessibility" | "seo" | "bestPractices" | "e2e" | "viewport" | "visual";
37
+ severity: "critical" | "high" | "medium";
38
+ title: string;
39
+ description: string;
40
+ action: string;
41
+ }
42
+ export interface VerificationEvidence {
43
+ input: VerificationInput;
44
+ thresholds: LighthouseThresholds;
45
+ buildPassed: boolean;
46
+ e2ePassedAll: boolean;
47
+ hasE2EData: boolean;
48
+ hasMultiViewportData: boolean;
49
+ multiViewportPassed: boolean;
50
+ hasVisualDiffData: boolean;
51
+ visualDiffPassed: boolean;
52
+ lighthousePassed: boolean;
53
+ }
54
+ export interface VerificationReport {
55
+ tier: VerificationTier;
56
+ verdict: ReleaseVerdict;
57
+ confidence: "low" | "medium" | "high";
58
+ summary: string;
59
+ grade: VerificationGrade;
60
+ blockers: VerificationFinding[];
61
+ warnings: VerificationFinding[];
62
+ passes: VerificationCheck[];
63
+ nextActions: string[];
64
+ failureEvidence: string[];
65
+ evidence: VerificationEvidence;
66
+ }
67
+ export interface TierVerificationView {
68
+ tier: VerificationTier;
69
+ question: string;
70
+ verdict: ReleaseVerdict;
71
+ confidence: "low" | "medium" | "high";
72
+ summary: string;
73
+ grade: VerificationGrade;
74
+ blockers: VerificationFinding[];
75
+ warnings: VerificationFinding[];
76
+ passes: VerificationCheck[];
77
+ nextActions: string[];
78
+ failureEvidence: string[];
79
+ showDetailedLighthouse: boolean;
80
+ showDetailedE2E: boolean;
81
+ showReportExport: boolean;
82
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "laxy-verify",
3
- "version": "1.1.7",
3
+ "version": "1.1.8",
4
4
  "description": "Frontend quality gate: build + Lighthouse verification",
5
5
  "type": "commonjs",
6
6
  "bin": {