laxy-verify 1.2.0 → 1.2.2
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 +12 -17
- package/dist/audit/broken-links.d.ts +21 -21
- package/dist/audit/broken-links.js +86 -86
- package/dist/auth.d.ts +11 -11
- package/dist/auth.js +222 -222
- package/dist/cli.js +868 -806
- package/dist/comment.d.ts +21 -21
- package/dist/comment.js +125 -125
- package/dist/config.d.ts +13 -0
- package/dist/config.js +43 -3
- package/dist/crawler.d.ts +36 -36
- package/dist/crawler.js +357 -357
- package/dist/e2e.d.ts +49 -49
- package/dist/e2e.js +565 -565
- package/dist/entitlement.d.ts +11 -11
- package/dist/entitlement.js +90 -90
- package/dist/init.js +87 -87
- package/dist/multi-viewport.d.ts +31 -31
- package/dist/multi-viewport.js +298 -298
- package/dist/playwright-runner.d.ts +16 -16
- package/dist/playwright-runner.js +208 -208
- package/dist/report-markdown.d.ts +39 -39
- package/dist/report-markdown.js +386 -386
- package/dist/security-audit.d.ts +9 -9
- package/dist/security-audit.js +64 -64
- package/dist/serve.d.ts +13 -13
- package/dist/serve.js +196 -196
- package/dist/trend.d.ts +50 -50
- package/dist/trend.js +148 -148
- package/dist/verification-core/index.d.ts +3 -3
- package/dist/verification-core/index.js +19 -19
- package/dist/verification-core/report.d.ts +14 -14
- package/dist/verification-core/report.js +409 -409
- package/dist/verification-core/tier-policy.d.ts +13 -13
- package/dist/verification-core/tier-policy.js +60 -60
- package/dist/verification-core/types.d.ts +108 -108
- package/dist/verification-core/types.js +2 -2
- package/dist/visual-diff.d.ts +26 -26
- package/dist/visual-diff.js +178 -178
- package/package.json +1 -1
package/dist/comment.d.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import type { TrendDelta } from "./trend.js";
|
|
2
|
-
interface LaxyResult {
|
|
3
|
-
grade: string;
|
|
4
|
-
lighthouse: {
|
|
5
|
-
performance: number;
|
|
6
|
-
accessibility: number;
|
|
7
|
-
seo: number;
|
|
8
|
-
bestPractices: number;
|
|
9
|
-
runs: number;
|
|
10
|
-
} | null;
|
|
11
|
-
thresholds: {
|
|
12
|
-
performance: number;
|
|
13
|
-
accessibility: number;
|
|
14
|
-
seo: number;
|
|
15
|
-
bestPractices: number;
|
|
16
|
-
};
|
|
17
|
-
exitCode: number;
|
|
18
|
-
config_fail_on: string;
|
|
19
|
-
}
|
|
20
|
-
export declare function postPRComment(result: LaxyResult, trend?: TrendDelta | null): Promise<void>;
|
|
21
|
-
export {};
|
|
1
|
+
import type { TrendDelta } from "./trend.js";
|
|
2
|
+
interface LaxyResult {
|
|
3
|
+
grade: string;
|
|
4
|
+
lighthouse: {
|
|
5
|
+
performance: number;
|
|
6
|
+
accessibility: number;
|
|
7
|
+
seo: number;
|
|
8
|
+
bestPractices: number;
|
|
9
|
+
runs: number;
|
|
10
|
+
} | null;
|
|
11
|
+
thresholds: {
|
|
12
|
+
performance: number;
|
|
13
|
+
accessibility: number;
|
|
14
|
+
seo: number;
|
|
15
|
+
bestPractices: number;
|
|
16
|
+
};
|
|
17
|
+
exitCode: number;
|
|
18
|
+
config_fail_on: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function postPRComment(result: LaxyResult, trend?: TrendDelta | null): Promise<void>;
|
|
21
|
+
export {};
|
package/dist/comment.js
CHANGED
|
@@ -1,106 +1,106 @@
|
|
|
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.postPRComment = postPRComment;
|
|
37
|
-
const fs = __importStar(require("node:fs"));
|
|
38
|
-
const github_js_1 = require("./github.js");
|
|
39
|
-
const trend_js_1 = require("./trend.js");
|
|
40
|
-
function renderTrendTable(delta) {
|
|
41
|
-
const lines = [
|
|
42
|
-
"| Check | This PR | vs Base |",
|
|
43
|
-
"|---|---|---|",
|
|
44
|
-
];
|
|
45
|
-
const gradeUp = (0, trend_js_1.gradeIndex)(delta.grade.current) > (0, trend_js_1.gradeIndex)(delta.grade.base);
|
|
46
|
-
const gradeDown = (0, trend_js_1.gradeIndex)(delta.grade.current) < (0, trend_js_1.gradeIndex)(delta.grade.base);
|
|
47
|
-
const gradeTrendStr = gradeUp ? ` ↑ (was ${delta.grade.base})` :
|
|
48
|
-
gradeDown ? ` ↓ (was ${delta.grade.base})` :
|
|
49
|
-
"";
|
|
50
|
-
lines.push(`| Grade | **${delta.grade.current}**${gradeTrendStr} | ${delta.grade.base} |`);
|
|
51
|
-
function fmtDelta(d) {
|
|
52
|
-
if (d === null)
|
|
53
|
-
return "—";
|
|
54
|
-
if (d > 0)
|
|
55
|
-
return `+${d}`;
|
|
56
|
-
if (d < 0)
|
|
57
|
-
return `${d}`;
|
|
58
|
-
return "—";
|
|
59
|
-
}
|
|
60
|
-
if (delta.performance.current !== null) {
|
|
61
|
-
lines.push(`| Performance | ${delta.performance.current} | ${fmtDelta(delta.performance.delta)} |`);
|
|
62
|
-
}
|
|
63
|
-
if (delta.accessibility.current !== null) {
|
|
64
|
-
lines.push(`| Accessibility | ${delta.accessibility.current} | ${fmtDelta(delta.accessibility.delta)} |`);
|
|
65
|
-
}
|
|
66
|
-
if (delta.seo.current !== null) {
|
|
67
|
-
lines.push(`| SEO | ${delta.seo.current} | ${fmtDelta(delta.seo.delta)} |`);
|
|
68
|
-
}
|
|
69
|
-
if (delta.bestPractices.current !== null) {
|
|
70
|
-
lines.push(`| Best Practices | ${delta.bestPractices.current} | ${fmtDelta(delta.bestPractices.delta)} |`);
|
|
71
|
-
}
|
|
72
|
-
if (delta.e2e.current !== null) {
|
|
73
|
-
lines.push(`| E2E | ${delta.e2e.current} | ${delta.e2e.base ?? "—"} (base) |`);
|
|
74
|
-
}
|
|
75
|
-
return `${lines.join("\n")}\n`;
|
|
76
|
-
}
|
|
77
|
-
async function postPRComment(result, trend) {
|
|
78
|
-
const ctx = (0, github_js_1.getGitHubContext)();
|
|
79
|
-
if (!ctx || ctx.eventName !== "pull_request")
|
|
80
|
-
return;
|
|
81
|
-
let prNumber = 0;
|
|
82
|
-
if (ctx.eventPath && fs.existsSync(ctx.eventPath)) {
|
|
83
|
-
const event = JSON.parse(fs.readFileSync(ctx.eventPath, "utf-8"));
|
|
84
|
-
prNumber = event.pull_request?.number ?? 0;
|
|
85
|
-
}
|
|
86
|
-
if (!prNumber)
|
|
87
|
-
return;
|
|
88
|
-
const grade = result.grade ?? "Unverified";
|
|
89
|
-
const lh = result.lighthouse;
|
|
90
|
-
const t = result.thresholds;
|
|
91
|
-
let lhTable = "";
|
|
92
|
-
if (lh) {
|
|
93
|
-
lhTable = `| Performance | Accessibility | SEO | Best Practices |\n|---|---|---|---|\n| ${lh.performance} / ${t.performance} | ${lh.accessibility} / ${t.accessibility} | ${lh.seo} / ${t.seo} | ${lh.bestPractices} / ${t.bestPractices} |\n\n`;
|
|
94
|
-
}
|
|
95
|
-
const trendTable = trend ? `${renderTrendTable(trend)}\n` : "";
|
|
96
|
-
const passed = result.exitCode === 0;
|
|
97
|
-
const statusLabel = passed ? "[PASS]" : "[HOLD]";
|
|
98
|
-
const headline = passed ? "No deployment blockers found" : "Deployment blockers found";
|
|
99
|
-
const summary = passed
|
|
100
|
-
? `This PR passed the current verification gate. Grade summary: **${grade}**.`
|
|
101
|
-
: grade === "Unverified"
|
|
102
|
-
? "The production build failed or verification could not complete, so this PR should be held."
|
|
103
|
-
: `This PR did not clear the current verification gate. Grade summary: **${grade}**.`;
|
|
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.postPRComment = postPRComment;
|
|
37
|
+
const fs = __importStar(require("node:fs"));
|
|
38
|
+
const github_js_1 = require("./github.js");
|
|
39
|
+
const trend_js_1 = require("./trend.js");
|
|
40
|
+
function renderTrendTable(delta) {
|
|
41
|
+
const lines = [
|
|
42
|
+
"| Check | This PR | vs Base |",
|
|
43
|
+
"|---|---|---|",
|
|
44
|
+
];
|
|
45
|
+
const gradeUp = (0, trend_js_1.gradeIndex)(delta.grade.current) > (0, trend_js_1.gradeIndex)(delta.grade.base);
|
|
46
|
+
const gradeDown = (0, trend_js_1.gradeIndex)(delta.grade.current) < (0, trend_js_1.gradeIndex)(delta.grade.base);
|
|
47
|
+
const gradeTrendStr = gradeUp ? ` ↑ (was ${delta.grade.base})` :
|
|
48
|
+
gradeDown ? ` ↓ (was ${delta.grade.base})` :
|
|
49
|
+
"";
|
|
50
|
+
lines.push(`| Grade | **${delta.grade.current}**${gradeTrendStr} | ${delta.grade.base} |`);
|
|
51
|
+
function fmtDelta(d) {
|
|
52
|
+
if (d === null)
|
|
53
|
+
return "—";
|
|
54
|
+
if (d > 0)
|
|
55
|
+
return `+${d}`;
|
|
56
|
+
if (d < 0)
|
|
57
|
+
return `${d}`;
|
|
58
|
+
return "—";
|
|
59
|
+
}
|
|
60
|
+
if (delta.performance.current !== null) {
|
|
61
|
+
lines.push(`| Performance | ${delta.performance.current} | ${fmtDelta(delta.performance.delta)} |`);
|
|
62
|
+
}
|
|
63
|
+
if (delta.accessibility.current !== null) {
|
|
64
|
+
lines.push(`| Accessibility | ${delta.accessibility.current} | ${fmtDelta(delta.accessibility.delta)} |`);
|
|
65
|
+
}
|
|
66
|
+
if (delta.seo.current !== null) {
|
|
67
|
+
lines.push(`| SEO | ${delta.seo.current} | ${fmtDelta(delta.seo.delta)} |`);
|
|
68
|
+
}
|
|
69
|
+
if (delta.bestPractices.current !== null) {
|
|
70
|
+
lines.push(`| Best Practices | ${delta.bestPractices.current} | ${fmtDelta(delta.bestPractices.delta)} |`);
|
|
71
|
+
}
|
|
72
|
+
if (delta.e2e.current !== null) {
|
|
73
|
+
lines.push(`| E2E | ${delta.e2e.current} | ${delta.e2e.base ?? "—"} (base) |`);
|
|
74
|
+
}
|
|
75
|
+
return `${lines.join("\n")}\n`;
|
|
76
|
+
}
|
|
77
|
+
async function postPRComment(result, trend) {
|
|
78
|
+
const ctx = (0, github_js_1.getGitHubContext)();
|
|
79
|
+
if (!ctx || ctx.eventName !== "pull_request")
|
|
80
|
+
return;
|
|
81
|
+
let prNumber = 0;
|
|
82
|
+
if (ctx.eventPath && fs.existsSync(ctx.eventPath)) {
|
|
83
|
+
const event = JSON.parse(fs.readFileSync(ctx.eventPath, "utf-8"));
|
|
84
|
+
prNumber = event.pull_request?.number ?? 0;
|
|
85
|
+
}
|
|
86
|
+
if (!prNumber)
|
|
87
|
+
return;
|
|
88
|
+
const grade = result.grade ?? "Unverified";
|
|
89
|
+
const lh = result.lighthouse;
|
|
90
|
+
const t = result.thresholds;
|
|
91
|
+
let lhTable = "";
|
|
92
|
+
if (lh) {
|
|
93
|
+
lhTable = `| Performance | Accessibility | SEO | Best Practices |\n|---|---|---|---|\n| ${lh.performance} / ${t.performance} | ${lh.accessibility} / ${t.accessibility} | ${lh.seo} / ${t.seo} | ${lh.bestPractices} / ${t.bestPractices} |\n\n`;
|
|
94
|
+
}
|
|
95
|
+
const trendTable = trend ? `${renderTrendTable(trend)}\n` : "";
|
|
96
|
+
const passed = result.exitCode === 0;
|
|
97
|
+
const statusLabel = passed ? "[PASS]" : "[HOLD]";
|
|
98
|
+
const headline = passed ? "No deployment blockers found" : "Deployment blockers found";
|
|
99
|
+
const summary = passed
|
|
100
|
+
? `This PR passed the current verification gate. Grade summary: **${grade}**.`
|
|
101
|
+
: grade === "Unverified"
|
|
102
|
+
? "The production build failed or verification could not complete, so this PR should be held."
|
|
103
|
+
: `This PR did not clear the current verification gate. Grade summary: **${grade}**.`;
|
|
104
104
|
const body = `## ${statusLabel} Laxy Verify: ${headline}
|
|
105
105
|
|
|
106
106
|
${summary}
|
|
@@ -110,25 +110,25 @@ ${trendTable}${lhTable}**Fail-on threshold**: ${result.config_fail_on ?? "bronze
|
|
|
110
110
|
Grade remains available for automation, but this check should be read first as a merge and release blocker gate.
|
|
111
111
|
|
|
112
112
|
---
|
|
113
|
-
[Open laxy-verify docs](https://github.com/SUNgm24/Laxy/tree/main/laxy-verify)`;
|
|
114
|
-
const [owner, repo] = ctx.repository.split("/");
|
|
115
|
-
const url = `https://api.github.com/repos/${owner}/${repo}/issues/${prNumber}/comments`;
|
|
116
|
-
try {
|
|
117
|
-
const res = await fetch(url, {
|
|
118
|
-
method: "POST",
|
|
119
|
-
headers: {
|
|
120
|
-
Authorization: `Bearer ${ctx.token}`,
|
|
121
|
-
Accept: "application/vnd.github.v3+json",
|
|
122
|
-
"Content-Type": "application/json",
|
|
123
|
-
},
|
|
124
|
-
body: JSON.stringify({ body }),
|
|
125
|
-
});
|
|
126
|
-
if (!res.ok) {
|
|
127
|
-
console.warn(`GitHub PR comment API returned ${res.status}; skipping comment.`);
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
catch (err) {
|
|
132
|
-
console.warn(`GitHub PR comment request failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
113
|
+
[Open laxy-verify docs](https://github.com/SUNgm24/Laxy/tree/main/laxy-verify)`;
|
|
114
|
+
const [owner, repo] = ctx.repository.split("/");
|
|
115
|
+
const url = `https://api.github.com/repos/${owner}/${repo}/issues/${prNumber}/comments`;
|
|
116
|
+
try {
|
|
117
|
+
const res = await fetch(url, {
|
|
118
|
+
method: "POST",
|
|
119
|
+
headers: {
|
|
120
|
+
Authorization: `Bearer ${ctx.token}`,
|
|
121
|
+
Accept: "application/vnd.github.v3+json",
|
|
122
|
+
"Content-Type": "application/json",
|
|
123
|
+
},
|
|
124
|
+
body: JSON.stringify({ body }),
|
|
125
|
+
});
|
|
126
|
+
if (!res.ok) {
|
|
127
|
+
console.warn(`GitHub PR comment API returned ${res.status}; skipping comment.`);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
console.warn(`GitHub PR comment request failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
package/dist/config.d.ts
CHANGED
|
@@ -38,6 +38,18 @@ export interface LaxyConfig {
|
|
|
38
38
|
export declare class ConfigParseError extends Error {
|
|
39
39
|
constructor(msg: string);
|
|
40
40
|
}
|
|
41
|
+
export interface TeamThresholdsConfig {
|
|
42
|
+
performance: number;
|
|
43
|
+
accessibility: number;
|
|
44
|
+
seo: number;
|
|
45
|
+
best_practices: number;
|
|
46
|
+
fail_on: FailOn;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 로그인된 CLI 토큰으로 팀 공통 임계값을 서버에서 가져온다.
|
|
50
|
+
* 토큰 없음 / 팀 없음 / 네트워크 오류 시 null 반환 (graceful degradation).
|
|
51
|
+
*/
|
|
52
|
+
export declare function fetchTeamThresholds(): Promise<TeamThresholdsConfig | null>;
|
|
41
53
|
export interface LoadConfigOptions {
|
|
42
54
|
dir: string;
|
|
43
55
|
configPath?: string;
|
|
@@ -46,6 +58,7 @@ export interface LoadConfigOptions {
|
|
|
46
58
|
skipLighthouse?: boolean;
|
|
47
59
|
};
|
|
48
60
|
ciMode: boolean;
|
|
61
|
+
teamThresholds?: TeamThresholdsConfig | null;
|
|
49
62
|
}
|
|
50
63
|
export declare function loadConfig(options: LoadConfigOptions): LaxyConfig & {
|
|
51
64
|
ciMode: boolean;
|
package/dist/config.js
CHANGED
|
@@ -34,10 +34,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.ConfigParseError = void 0;
|
|
37
|
+
exports.fetchTeamThresholds = fetchTeamThresholds;
|
|
37
38
|
exports.loadConfig = loadConfig;
|
|
38
39
|
const fs = __importStar(require("node:fs"));
|
|
39
40
|
const path = __importStar(require("node:path"));
|
|
40
41
|
const yaml = __importStar(require("js-yaml"));
|
|
42
|
+
const auth_js_1 = require("./auth.js");
|
|
41
43
|
const DEFAULT_CONFIG = {
|
|
42
44
|
framework: "auto",
|
|
43
45
|
build_command: "",
|
|
@@ -164,12 +166,46 @@ function parseYaml(filePath) {
|
|
|
164
166
|
}
|
|
165
167
|
return result;
|
|
166
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* 로그인된 CLI 토큰으로 팀 공통 임계값을 서버에서 가져온다.
|
|
171
|
+
* 토큰 없음 / 팀 없음 / 네트워크 오류 시 null 반환 (graceful degradation).
|
|
172
|
+
*/
|
|
173
|
+
async function fetchTeamThresholds() {
|
|
174
|
+
const token = (0, auth_js_1.loadToken)();
|
|
175
|
+
if (!token)
|
|
176
|
+
return null;
|
|
177
|
+
try {
|
|
178
|
+
const res = await fetch(`${auth_js_1.LAXY_API_URL}/api/v1/team-thresholds`, {
|
|
179
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
180
|
+
signal: AbortSignal.timeout(5000),
|
|
181
|
+
});
|
|
182
|
+
if (!res.ok)
|
|
183
|
+
return null;
|
|
184
|
+
const data = (await res.json());
|
|
185
|
+
return data.thresholds ?? null;
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
167
191
|
function loadConfig(options) {
|
|
168
192
|
const configPath = options.configPath ?? path.join(options.dir, ".laxy.yml");
|
|
169
193
|
let base = {};
|
|
170
|
-
|
|
194
|
+
const hasLocalConfig = fs.existsSync(configPath);
|
|
195
|
+
if (hasLocalConfig) {
|
|
171
196
|
base = parseYaml(configPath);
|
|
172
197
|
}
|
|
198
|
+
// 팀 임계값: 로컬 .laxy.yml에 thresholds가 없을 때만 적용
|
|
199
|
+
const team = options.teamThresholds ?? null;
|
|
200
|
+
const teamThresholdFallback = (!hasLocalConfig || !base.thresholds) && team
|
|
201
|
+
? {
|
|
202
|
+
performance: team.performance,
|
|
203
|
+
accessibility: team.accessibility,
|
|
204
|
+
seo: team.seo,
|
|
205
|
+
bestPractices: team.best_practices,
|
|
206
|
+
}
|
|
207
|
+
: {};
|
|
208
|
+
const teamFailOnFallback = (!hasLocalConfig || !base.fail_on) && team ? team.fail_on : undefined;
|
|
173
209
|
const config = {
|
|
174
210
|
...DEFAULT_CONFIG,
|
|
175
211
|
framework: base.framework ?? DEFAULT_CONFIG.framework,
|
|
@@ -180,14 +216,18 @@ function loadConfig(options) {
|
|
|
180
216
|
build_timeout: base.build_timeout ?? DEFAULT_CONFIG.build_timeout,
|
|
181
217
|
dev_timeout: base.dev_timeout ?? DEFAULT_CONFIG.dev_timeout,
|
|
182
218
|
lighthouse_runs: base.lighthouse_runs ?? DEFAULT_CONFIG.lighthouse_runs,
|
|
183
|
-
fail_on: base.fail_on ?? DEFAULT_CONFIG.fail_on,
|
|
219
|
+
fail_on: base.fail_on ?? teamFailOnFallback ?? DEFAULT_CONFIG.fail_on,
|
|
184
220
|
scenarios: base.scenarios,
|
|
185
221
|
crawl: base.crawl ?? DEFAULT_CONFIG.crawl,
|
|
186
222
|
max_crawl_depth: base.max_crawl_depth ?? DEFAULT_CONFIG.max_crawl_depth,
|
|
187
223
|
max_crawl_pages: base.max_crawl_pages ?? DEFAULT_CONFIG.max_crawl_pages,
|
|
188
224
|
browsers: base.browsers ?? DEFAULT_CONFIG.browsers,
|
|
189
225
|
};
|
|
190
|
-
config.thresholds = {
|
|
226
|
+
config.thresholds = {
|
|
227
|
+
...DEFAULT_CONFIG.thresholds,
|
|
228
|
+
...teamThresholdFallback,
|
|
229
|
+
...(base.thresholds ?? {}),
|
|
230
|
+
};
|
|
191
231
|
// CLI flag overrides
|
|
192
232
|
if (options.cliFlags?.failOn !== undefined) {
|
|
193
233
|
if (!VALID_FAIL_ON.includes(options.cliFlags.failOn)) {
|
package/dist/crawler.d.ts
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
import type { E2EScenario } from "./e2e.js";
|
|
2
|
-
import type { VerificationTier } from "./verification-core/types.js";
|
|
3
|
-
export interface CrawlPage {
|
|
4
|
-
url: string;
|
|
5
|
-
path: string;
|
|
6
|
-
title: string;
|
|
7
|
-
forms: CrawlForm[];
|
|
8
|
-
buttons: string[];
|
|
9
|
-
internalLinks: string[];
|
|
10
|
-
hasConsoleErrors: boolean;
|
|
11
|
-
consoleErrors: string[];
|
|
12
|
-
}
|
|
13
|
-
export interface CrawlForm {
|
|
14
|
-
selector: string;
|
|
15
|
-
inputs: {
|
|
16
|
-
selector: string;
|
|
17
|
-
type: string;
|
|
18
|
-
placeholder?: string;
|
|
19
|
-
}[];
|
|
20
|
-
submitSelector?: string;
|
|
21
|
-
}
|
|
22
|
-
export interface CrawlResult {
|
|
23
|
-
pages: CrawlPage[];
|
|
24
|
-
totalLinks: number;
|
|
25
|
-
crawledCount: number;
|
|
26
|
-
}
|
|
27
|
-
export interface CrawlOptions {
|
|
28
|
-
maxDepth?: number;
|
|
29
|
-
maxPages?: number;
|
|
30
|
-
timeout?: number;
|
|
31
|
-
}
|
|
32
|
-
export declare function crawlApp(baseUrl: string, options?: CrawlOptions): Promise<CrawlResult>;
|
|
33
|
-
/**
|
|
34
|
-
* Generate E2E scenarios from crawl results.
|
|
35
|
-
*/
|
|
36
|
-
export declare function buildScenariosFromCrawl(crawlResult: CrawlResult, tier: VerificationTier): E2EScenario[];
|
|
1
|
+
import type { E2EScenario } from "./e2e.js";
|
|
2
|
+
import type { VerificationTier } from "./verification-core/types.js";
|
|
3
|
+
export interface CrawlPage {
|
|
4
|
+
url: string;
|
|
5
|
+
path: string;
|
|
6
|
+
title: string;
|
|
7
|
+
forms: CrawlForm[];
|
|
8
|
+
buttons: string[];
|
|
9
|
+
internalLinks: string[];
|
|
10
|
+
hasConsoleErrors: boolean;
|
|
11
|
+
consoleErrors: string[];
|
|
12
|
+
}
|
|
13
|
+
export interface CrawlForm {
|
|
14
|
+
selector: string;
|
|
15
|
+
inputs: {
|
|
16
|
+
selector: string;
|
|
17
|
+
type: string;
|
|
18
|
+
placeholder?: string;
|
|
19
|
+
}[];
|
|
20
|
+
submitSelector?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface CrawlResult {
|
|
23
|
+
pages: CrawlPage[];
|
|
24
|
+
totalLinks: number;
|
|
25
|
+
crawledCount: number;
|
|
26
|
+
}
|
|
27
|
+
export interface CrawlOptions {
|
|
28
|
+
maxDepth?: number;
|
|
29
|
+
maxPages?: number;
|
|
30
|
+
timeout?: number;
|
|
31
|
+
}
|
|
32
|
+
export declare function crawlApp(baseUrl: string, options?: CrawlOptions): Promise<CrawlResult>;
|
|
33
|
+
/**
|
|
34
|
+
* Generate E2E scenarios from crawl results.
|
|
35
|
+
*/
|
|
36
|
+
export declare function buildScenariosFromCrawl(crawlResult: CrawlResult, tier: VerificationTier): E2EScenario[];
|