laxy-verify 1.2.2 → 1.3.0

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.
@@ -1,11 +1,15 @@
1
- export interface EntitlementFeatures {
2
- plan: string;
3
- github_actions: boolean;
4
- queue_priority: boolean;
5
- parallel_execution: boolean;
6
- }
7
- export type TestablePlan = "free" | "pro" | "team";
8
- export declare function normalizePlan(plan?: string | null): TestablePlan;
9
- export declare function getEntitlements(): Promise<EntitlementFeatures>;
10
- export declare function applyPlanOverride(features: EntitlementFeatures, overridePlan?: TestablePlan): EntitlementFeatures;
11
- export declare function printPlanBanner(features: EntitlementFeatures): void;
1
+ export interface EntitlementFeatures {
2
+ plan: string;
3
+ github_actions: boolean;
4
+ queue_priority: boolean;
5
+ parallel_execution: boolean;
6
+ ai_failure_analysis: boolean;
7
+ compare_env: boolean;
8
+ share_result: boolean;
9
+ history_trend: boolean;
10
+ }
11
+ export type TestablePlan = "free" | "pro" | "team";
12
+ export declare function normalizePlan(plan?: string | null): TestablePlan;
13
+ export declare function getEntitlements(): Promise<EntitlementFeatures>;
14
+ export declare function applyPlanOverride(features: EntitlementFeatures, overridePlan?: TestablePlan): EntitlementFeatures;
15
+ export declare function printPlanBanner(features: EntitlementFeatures): void;
@@ -1,90 +1,98 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.normalizePlan = normalizePlan;
4
- exports.getEntitlements = getEntitlements;
5
- exports.applyPlanOverride = applyPlanOverride;
6
- exports.printPlanBanner = printPlanBanner;
7
- /**
8
- * Fetches account entitlements for laxy-verify.
9
- *
10
- * The CLI asks /api/v1/cli-entitlement for the current plan and enabled automation flags.
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 the unlocked verification defaults.
13
- */
14
- const auth_js_1 = require("./auth.js");
15
- const FREE_FEATURES = {
16
- plan: "free",
17
- // Automation features — not available without login
18
- github_actions: false,
19
- queue_priority: false,
20
- parallel_execution: false,
21
- };
22
- let cache = null;
23
- const CACHE_TTL_MS = 5 * 60 * 1000;
24
- const PLAN_RANK = {
25
- free: 0,
26
- pro: 1,
27
- team: 2,
28
- };
29
- function normalizePlan(plan) {
30
- if (plan === "pro")
31
- return "pro";
32
- if (plan === "team")
33
- return "team";
34
- return "free";
35
- }
36
- function getPlanRank(plan) {
37
- return PLAN_RANK[plan ?? "free"] ?? 0;
38
- }
39
- async function getEntitlements() {
40
- if (cache && Date.now() - cache.fetchedAt < CACHE_TTL_MS) {
41
- return cache.features;
42
- }
43
- const token = (0, auth_js_1.loadToken)();
44
- if (!token)
45
- return FREE_FEATURES;
46
- try {
47
- const res = await fetch(`${auth_js_1.LAXY_API_URL}/api/v1/cli-entitlement`, {
48
- headers: { Authorization: `Bearer ${token}` },
49
- });
50
- if (!res.ok) {
51
- if (res.status === 401) {
52
- console.error(" Note: your CLI session is no longer valid. Run laxy-verify login again to refresh your account metadata.");
53
- }
54
- return FREE_FEATURES;
55
- }
56
- const features = (await res.json());
57
- cache = { features, fetchedAt: Date.now() };
58
- return features;
59
- }
60
- catch {
61
- return FREE_FEATURES;
62
- }
63
- }
64
- function applyPlanOverride(features, overridePlan) {
65
- if (!overridePlan)
66
- return features;
67
- const isTeam = overridePlan === "team";
68
- const isPro = overridePlan === "pro" || overridePlan === "team";
69
- return {
70
- ...features,
71
- plan: overridePlan,
72
- // All verification features run on every plan
73
- // github_actions — Pro and Team
74
- github_actions: isPro,
75
- // queue_priority, parallel_execution — Team only
76
- queue_priority: isTeam,
77
- parallel_execution: isTeam,
78
- };
79
- }
80
- function printPlanBanner(features) {
81
- const planLabels = {
82
- free: "Free",
83
- pro: "Pro",
84
- team: "Team",
85
- };
86
- const label = planLabels[features.plan] ?? features.plan;
87
- if (features.plan !== "free") {
88
- console.log(` Plan: ${label}`);
89
- }
90
- }
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizePlan = normalizePlan;
4
+ exports.getEntitlements = getEntitlements;
5
+ exports.applyPlanOverride = applyPlanOverride;
6
+ exports.printPlanBanner = printPlanBanner;
7
+ /**
8
+ * Fetches account entitlements for laxy-verify.
9
+ *
10
+ * The CLI asks /api/v1/cli-entitlement for the current plan and enabled automation flags.
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 the unlocked verification defaults.
13
+ */
14
+ const auth_js_1 = require("./auth.js");
15
+ const FREE_FEATURES = {
16
+ plan: "free",
17
+ // Automation features — not available without login
18
+ github_actions: false,
19
+ queue_priority: false,
20
+ parallel_execution: false,
21
+ ai_failure_analysis: false,
22
+ compare_env: false,
23
+ share_result: false,
24
+ history_trend: false,
25
+ };
26
+ let cache = null;
27
+ const CACHE_TTL_MS = 5 * 60 * 1000;
28
+ const PLAN_RANK = {
29
+ free: 0,
30
+ pro: 1,
31
+ team: 2,
32
+ };
33
+ function normalizePlan(plan) {
34
+ if (plan === "pro")
35
+ return "pro";
36
+ if (plan === "team")
37
+ return "team";
38
+ return "free";
39
+ }
40
+ function getPlanRank(plan) {
41
+ return PLAN_RANK[plan ?? "free"] ?? 0;
42
+ }
43
+ async function getEntitlements() {
44
+ if (cache && Date.now() - cache.fetchedAt < CACHE_TTL_MS) {
45
+ return cache.features;
46
+ }
47
+ const token = (0, auth_js_1.loadToken)();
48
+ if (!token)
49
+ return FREE_FEATURES;
50
+ try {
51
+ const res = await fetch(`${auth_js_1.LAXY_API_URL}/api/v1/cli-entitlement`, {
52
+ headers: { Authorization: `Bearer ${token}` },
53
+ });
54
+ if (!res.ok) {
55
+ if (res.status === 401) {
56
+ console.error(" Note: your CLI session is no longer valid. Run laxy-verify login again to refresh your account metadata.");
57
+ }
58
+ return FREE_FEATURES;
59
+ }
60
+ const features = (await res.json());
61
+ cache = { features, fetchedAt: Date.now() };
62
+ return features;
63
+ }
64
+ catch {
65
+ return FREE_FEATURES;
66
+ }
67
+ }
68
+ function applyPlanOverride(features, overridePlan) {
69
+ if (!overridePlan)
70
+ return features;
71
+ const isTeam = overridePlan === "team";
72
+ const isPro = overridePlan === "pro" || overridePlan === "team";
73
+ return {
74
+ ...features,
75
+ plan: overridePlan,
76
+ // All verification features run on every plan
77
+ // github_actions, ai_failure_analysis, compare_env, share_result, history_trend — Pro and Team
78
+ github_actions: isPro,
79
+ ai_failure_analysis: isPro,
80
+ compare_env: isPro,
81
+ share_result: isPro,
82
+ history_trend: isPro,
83
+ // queue_priority, parallel_execution — Team only
84
+ queue_priority: isTeam,
85
+ parallel_execution: isTeam,
86
+ };
87
+ }
88
+ function printPlanBanner(features) {
89
+ const planLabels = {
90
+ free: "Free",
91
+ pro: "Pro",
92
+ team: "Team",
93
+ };
94
+ const label = planLabels[features.plan] ?? features.plan;
95
+ if (features.plan !== "free") {
96
+ console.log(` Plan: ${label}`);
97
+ }
98
+ }
package/dist/init.js CHANGED
@@ -1,81 +1,147 @@
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.runInit = runInit;
37
- const fs = __importStar(require("node:fs"));
38
- const path = __importStar(require("node:path"));
39
- const detect_js_1 = require("./detect.js");
40
- function runInit(dir) {
41
- const laxyYmlPath = path.join(dir, ".laxy.yml");
42
- const workflowDir = path.join(dir, ".github", "workflows");
43
- const workflowPath = path.join(workflowDir, "laxy-verify.yml");
44
- const gitignorePath = path.join(dir, ".gitignore");
45
- if (fs.existsSync(laxyYmlPath)) {
46
- console.log(".laxy.yml already exists. Skipping. Delete it to regenerate.");
47
- }
48
- else {
49
- let detectedFramework = null;
50
- let detectedPort = 3000;
51
- try {
52
- const detected = (0, detect_js_1.detect)(dir);
53
- detectedFramework = detected.framework ?? "auto";
54
- detectedPort = detected.port;
55
- }
56
- catch {
57
- // Keep defaults when auto-detection fails.
58
- }
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.runInit = runInit;
37
+ const fs = __importStar(require("node:fs"));
38
+ const path = __importStar(require("node:path"));
39
+ const detect_js_1 = require("./detect.js");
40
+ const init_analysis_js_1 = require("./init-analysis.js");
41
+ function renderStringList(items, indent = " ") {
42
+ return items.map((item) => `${indent}- ${item}`).join("\n");
43
+ }
44
+ function renderScenarios() {
45
+ return `scenarios:
46
+ - name: "로그인 대시보드 접근"
47
+ steps:
48
+ - goto: /login
49
+ - fill: input[name=email]
50
+ with: test@example.com
51
+ - fill: input[name=password]
52
+ with: testpass123!
53
+ - click: button[type=submit]
54
+ - expect_visible: main`;
55
+ }
56
+ function runInit(dir) {
57
+ const laxyYmlPath = path.join(dir, ".laxy.yml");
58
+ const workflowDir = path.join(dir, ".github", "workflows");
59
+ const workflowPath = path.join(workflowDir, "laxy-verify.yml");
60
+ const gitignorePath = path.join(dir, ".gitignore");
61
+ if (fs.existsSync(laxyYmlPath)) {
62
+ console.log(".laxy.yml already exists. Skipping. Delete it to regenerate.");
63
+ }
64
+ else {
65
+ let detectedFramework = null;
66
+ let detectedPort = 3000;
67
+ const initAnalysis = (0, init_analysis_js_1.analyzeProjectForInit)(dir);
68
+ try {
69
+ const detected = (0, detect_js_1.detect)(dir);
70
+ detectedFramework = detected.framework ?? "auto";
71
+ detectedPort = detected.port;
72
+ }
73
+ catch {
74
+ // Keep defaults when auto-detection fails.
75
+ }
76
+ const extraRoutesBlock = initAnalysis.extraRoutes.length > 0
77
+ ? `extra_routes:\n${renderStringList(initAnalysis.extraRoutes)}\n\n`
78
+ : "";
79
+ const scenarioBlocks = initAnalysis.scenarios.length > 0
80
+ ? [
81
+ "scenarios:",
82
+ ...initAnalysis.scenarios.flatMap((scenario) => [
83
+ ` - name: "${scenario.name.replace(/"/g, '\\"')}"`,
84
+ " steps:",
85
+ ...scenario.steps.flatMap((step) => {
86
+ if (step.goto)
87
+ return [` - goto: ${step.goto}`];
88
+ if (step.fill && step.with !== undefined) {
89
+ return [
90
+ ` - fill: ${step.fill}`,
91
+ ` with: ${step.with}`,
92
+ ];
93
+ }
94
+ if (step.click)
95
+ return [` - click: ${step.click}`];
96
+ if (step.expect_visible)
97
+ return [` - expect_visible: ${step.expect_visible}`];
98
+ if (step.expect_text)
99
+ return [` - expect_text: ${step.expect_text}`];
100
+ if (step.wait !== undefined)
101
+ return [` - wait: ${step.wait}`];
102
+ return [];
103
+ }),
104
+ ]),
105
+ ].join("\n")
106
+ : renderScenarios();
59
107
  const ymlContent = `# Generated by laxy-verify --init
60
108
  # See https://github.com/SUNgm24/Laxy/tree/main/laxy-verify for full docs
61
109
  framework: ${detectedFramework} # auto-detected
62
110
  port: ${detectedPort}
63
111
  fail_on: bronze
112
+ lighthouse_runs: 3
64
113
 
65
114
  thresholds:
66
115
  performance: 70
67
116
  accessibility: 85
68
117
  seo: 80
69
118
  best_practices: 80
70
- `;
71
- fs.writeFileSync(laxyYmlPath, ymlContent, "utf-8");
72
- console.log("Created .laxy.yml");
73
- }
74
- if (fs.existsSync(workflowPath)) {
75
- console.log(".github/workflows/laxy-verify.yml already exists. Skipping.");
76
- }
77
- else {
78
- fs.mkdirSync(workflowDir, { recursive: true });
119
+
120
+ ${extraRoutesBlock}visual_diff:
121
+ pixelmatch_threshold: 0.1
122
+ warn_threshold: 30
123
+ rollback_threshold: 60
124
+ disable_animations: true
125
+ ignore_selectors:
126
+ - "[data-dynamic]"
127
+ - "[data-ignore-diff]"
128
+
129
+ ${scenarioBlocks}
130
+ `;
131
+ fs.writeFileSync(laxyYmlPath, ymlContent, "utf-8");
132
+ console.log("Created .laxy.yml");
133
+ if (initAnalysis.scenarios.length > 0) {
134
+ console.log(`Seeded ${initAnalysis.scenarios.length} draft E2E scenario(s) from your app structure.`);
135
+ }
136
+ if (initAnalysis.extraRoutes.length > 0) {
137
+ console.log(`Detected ${initAnalysis.extraRoutes.length} extra route(s) for SPA / multi-page coverage.`);
138
+ }
139
+ }
140
+ if (fs.existsSync(workflowPath)) {
141
+ console.log(".github/workflows/laxy-verify.yml already exists. Skipping.");
142
+ }
143
+ else {
144
+ fs.mkdirSync(workflowDir, { recursive: true });
79
145
  const workflowContent = `name: Laxy Verify
80
146
  on:
81
147
  pull_request:
@@ -132,23 +198,23 @@ jobs:
132
198
  path: .laxy-result.json
133
199
  retention-days: 30
134
200
  overwrite: true
135
- `;
136
- fs.writeFileSync(workflowPath, workflowContent, "utf-8");
137
- console.log("Created .github/workflows/laxy-verify.yml");
138
- }
139
- const gitignoreEntries = [".lighthouseci/", ".laxy-result.json"];
140
- if (fs.existsSync(gitignorePath)) {
141
- const existing = fs.readFileSync(gitignorePath, "utf-8");
142
- const lines = existing.split("\n").map((line) => line.trim());
143
- const missing = gitignoreEntries.filter((entry) => !lines.includes(entry));
144
- if (missing.length > 0) {
145
- fs.appendFileSync(gitignorePath, `${missing.join("\n")}\n`, "utf-8");
146
- console.log(`Appended to .gitignore: ${missing.join(", ")}`);
147
- }
148
- }
149
- else {
150
- fs.writeFileSync(gitignorePath, `${gitignoreEntries.join("\n")}\n`, "utf-8");
151
- console.log("Created .gitignore");
152
- }
153
- console.log("\n Done. Run 'npx laxy-verify .' to verify your project.");
154
- }
201
+ `;
202
+ fs.writeFileSync(workflowPath, workflowContent, "utf-8");
203
+ console.log("Created .github/workflows/laxy-verify.yml");
204
+ }
205
+ const gitignoreEntries = [".lighthouseci/", ".laxy-result.json"];
206
+ if (fs.existsSync(gitignorePath)) {
207
+ const existing = fs.readFileSync(gitignorePath, "utf-8");
208
+ const lines = existing.split("\n").map((line) => line.trim());
209
+ const missing = gitignoreEntries.filter((entry) => !lines.includes(entry));
210
+ if (missing.length > 0) {
211
+ fs.appendFileSync(gitignorePath, `${missing.join("\n")}\n`, "utf-8");
212
+ console.log(`Appended to .gitignore: ${missing.join(", ")}`);
213
+ }
214
+ }
215
+ else {
216
+ fs.writeFileSync(gitignorePath, `${gitignoreEntries.join("\n")}\n`, "utf-8");
217
+ console.log("Created .gitignore");
218
+ }
219
+ console.log("\n Done. Run 'npx laxy-verify .' to verify your project.");
220
+ }
@@ -1,7 +1,37 @@
1
- import type { LighthouseScores } from "./grade.js";
2
- interface LhResult {
3
- scores: LighthouseScores | null;
4
- errors: string[];
5
- }
6
- export declare function runLighthouse(port: number, runs: number): Promise<LhResult>;
7
- export {};
1
+ import type { LighthouseScores } from "./grade.js";
2
+ interface LhResult {
3
+ scores: LighthouseScores | null;
4
+ errors: string[];
5
+ }
6
+ export interface RouteScoreResult {
7
+ route: string;
8
+ scores: LighthouseScores | null;
9
+ errors: string[];
10
+ }
11
+ export interface MultiRouteLhResult {
12
+ /** Weighted average: root (/) = 2x weight, other routes = 1x */
13
+ aggregated: LighthouseScores | null;
14
+ perRoute: RouteScoreResult[];
15
+ /** True if every route that produced scores individually passed all thresholds */
16
+ allRoutesPass(thresholds: {
17
+ performance: number;
18
+ accessibility: number;
19
+ seo: number;
20
+ bestPractices: number;
21
+ }): boolean;
22
+ }
23
+ export declare function runLighthouse(port: number, runs: number, routePath?: string): Promise<LhResult>;
24
+ /** Run Lighthouse against an arbitrary external URL (Pro: compare-env feature). */
25
+ export declare function runLighthouseOnUrl(fullUrl: string, runs: number): Promise<LhResult>;
26
+ /**
27
+ * Run Lighthouse on multiple routes and aggregate results.
28
+ *
29
+ * Aggregation strategy (weighted average):
30
+ * - Root route "/" receives weight 2 (most visible page)
31
+ * - All other routes receive weight 1
32
+ *
33
+ * Gold eligibility: every route must individually pass all thresholds.
34
+ * Silver/Bronze eligibility: based on the weighted-average aggregate score.
35
+ */
36
+ export declare function runLighthouseOnRoutes(port: number, runs: number, routes: string[]): Promise<MultiRouteLhResult>;
37
+ export {};