laxy-verify 1.2.3 → 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.
- package/README.md +23 -0
- package/dist/audit/broken-links.d.ts +25 -25
- package/dist/audit/broken-links.js +97 -97
- package/dist/badge.d.ts +2 -1
- package/dist/badge.js +18 -14
- package/dist/cli.js +1246 -1233
- package/dist/config.d.ts +102 -102
- package/dist/config.js +360 -360
- package/dist/entitlement.d.ts +15 -13
- package/dist/entitlement.js +98 -94
- package/dist/init.js +132 -132
- package/dist/lighthouse.d.ts +37 -37
- package/dist/lighthouse.js +231 -231
- package/dist/report-markdown.d.ts +53 -53
- package/dist/report-markdown.js +407 -407
- package/dist/security-audit.d.ts +17 -17
- package/dist/security-audit.js +127 -127
- package/dist/verification-core/report.js +526 -526
- package/dist/verification-core/types.d.ts +164 -164
- package/dist/visual-diff.d.ts +33 -33
- package/dist/visual-diff.js +213 -213
- package/package.json +1 -1
- package/dist/ai-analysis.d.ts +0 -28
- package/dist/ai-analysis.js +0 -32
- package/dist/compare-env.d.ts +0 -23
- package/dist/compare-env.js +0 -55
- package/dist/init-analysis.d.ts +0 -6
- package/dist/init-analysis.js +0 -302
- package/dist/route-discovery.d.ts +0 -7
- package/dist/route-discovery.js +0 -108
package/dist/entitlement.d.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export
|
|
12
|
-
export declare function
|
|
13
|
-
export declare function
|
|
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;
|
package/dist/entitlement.js
CHANGED
|
@@ -1,94 +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
|
-
ai_failure_analysis: false,
|
|
22
|
-
compare_env: false,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (plan === "
|
|
35
|
-
return "
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
ai_failure_analysis
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
|
|
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,47 +1,47 @@
|
|
|
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() {
|
|
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
45
|
return `scenarios:
|
|
46
46
|
- name: "로그인 후 대시보드 접근"
|
|
47
47
|
steps:
|
|
@@ -51,59 +51,59 @@ function renderScenarios() {
|
|
|
51
51
|
- fill: input[name=password]
|
|
52
52
|
with: testpass123!
|
|
53
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();
|
|
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();
|
|
107
107
|
const ymlContent = `# Generated by laxy-verify --init
|
|
108
108
|
# See https://github.com/SUNgm24/Laxy/tree/main/laxy-verify for full docs
|
|
109
109
|
framework: ${detectedFramework} # auto-detected
|
|
@@ -127,21 +127,21 @@ ${extraRoutesBlock}visual_diff:
|
|
|
127
127
|
- "[data-ignore-diff]"
|
|
128
128
|
|
|
129
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 });
|
|
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 });
|
|
145
145
|
const workflowContent = `name: Laxy Verify
|
|
146
146
|
on:
|
|
147
147
|
pull_request:
|
|
@@ -198,23 +198,23 @@ jobs:
|
|
|
198
198
|
path: .laxy-result.json
|
|
199
199
|
retention-days: 30
|
|
200
200
|
overwrite: true
|
|
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
|
-
}
|
|
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
|
+
}
|
package/dist/lighthouse.d.ts
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
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 {};
|
|
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 {};
|