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.
@@ -1,302 +0,0 @@
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.analyzeProjectForInit = analyzeProjectForInit;
37
- const fs = __importStar(require("node:fs"));
38
- const path = __importStar(require("node:path"));
39
- const SOURCE_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
40
- const IGNORED_DIRS = new Set([
41
- ".git",
42
- ".next",
43
- ".turbo",
44
- ".vercel",
45
- ".output",
46
- ".laxy-verify",
47
- "coverage",
48
- "dist",
49
- "build",
50
- "node_modules",
51
- ]);
52
- function isSourceFile(filePath) {
53
- return SOURCE_EXTENSIONS.has(path.extname(filePath).toLowerCase());
54
- }
55
- function normalizeRoute(route) {
56
- const cleaned = route
57
- .replace(/\\u002F/gi, "/")
58
- .replace(/\\\//g, "/")
59
- .trim();
60
- if (!cleaned.startsWith("/"))
61
- return null;
62
- const normalized = cleaned.split("?")[0]?.split("#")[0]?.replace(/\/+/g, "/") ?? "/";
63
- if (!normalized || normalized === "/")
64
- return "/";
65
- if (normalized.startsWith("/_next/") || normalized.startsWith("/api/"))
66
- return null;
67
- if (/[.*:[\]]/.test(normalized))
68
- return null;
69
- if (/\.[a-z0-9]{2,8}$/i.test(normalized))
70
- return null;
71
- if (/\s/.test(normalized))
72
- return null;
73
- return normalized.endsWith("/") && normalized !== "/" ? normalized.slice(0, -1) : normalized;
74
- }
75
- function collectSourceFiles(rootDir, currentDir = rootDir, acc = []) {
76
- const entries = fs.readdirSync(currentDir, { withFileTypes: true });
77
- for (const entry of entries) {
78
- const fullPath = path.join(currentDir, entry.name);
79
- if (entry.isDirectory()) {
80
- if (IGNORED_DIRS.has(entry.name))
81
- continue;
82
- collectSourceFiles(rootDir, fullPath, acc);
83
- continue;
84
- }
85
- if (entry.isFile() && isSourceFile(fullPath)) {
86
- acc.push(fullPath);
87
- }
88
- }
89
- return acc;
90
- }
91
- function readFileSafe(filePath) {
92
- try {
93
- return fs.readFileSync(filePath, "utf-8");
94
- }
95
- catch {
96
- return "";
97
- }
98
- }
99
- function routeFromNextAppFile(filePath, baseDir) {
100
- const relative = path.relative(baseDir, filePath).replace(/\\/g, "/");
101
- if (!relative.startsWith("app/"))
102
- return null;
103
- if (!/\/page\.(t|j)sx?$/.test(relative) && !relative.endsWith("/page.mdx"))
104
- return null;
105
- const segments = relative
106
- .replace(/^app\//, "")
107
- .replace(/\/page\.(t|j)sx?$/, "")
108
- .replace(/\/page\.mdx$/, "")
109
- .split("/")
110
- .filter(Boolean)
111
- .filter((segment) => !segment.startsWith("("))
112
- .filter((segment) => !segment.startsWith("@"))
113
- .filter((segment) => segment !== "page")
114
- .flatMap((segment) => {
115
- if (segment === "index")
116
- return [];
117
- const cleaned = segment.replace(/\.(t|j)sx?$/, "");
118
- if (cleaned.startsWith("(") || cleaned.startsWith("[["))
119
- return [];
120
- if (cleaned.startsWith("["))
121
- return [];
122
- return cleaned ? [cleaned] : [];
123
- });
124
- const route = `/${segments.join("/")}`;
125
- return normalizeRoute(route);
126
- }
127
- function routeFromNextPagesFile(filePath, baseDir) {
128
- const relative = path.relative(baseDir, filePath).replace(/\\/g, "/");
129
- if (!relative.startsWith("pages/"))
130
- return null;
131
- if (!/\.(t|j)sx?$/.test(relative))
132
- return null;
133
- if (/^pages\/api\//.test(relative))
134
- return null;
135
- const withoutExt = relative.replace(/^pages\//, "").replace(/\.(t|j)sx?$/, "");
136
- const segments = withoutExt
137
- .split("/")
138
- .filter(Boolean)
139
- .filter((segment) => segment !== "index")
140
- .filter((segment) => !segment.startsWith("["));
141
- const route = `/${segments.join("/")}`;
142
- return normalizeRoute(route);
143
- }
144
- function collectRoutesFromFiles(rootDir, files) {
145
- const routes = new Map();
146
- for (const filePath of files) {
147
- const nextAppRoute = routeFromNextAppFile(filePath, rootDir);
148
- if (nextAppRoute)
149
- routes.set(nextAppRoute, filePath);
150
- const nextPagesRoute = routeFromNextPagesFile(filePath, rootDir);
151
- if (nextPagesRoute)
152
- routes.set(nextPagesRoute, filePath);
153
- const content = readFileSafe(filePath);
154
- if (!content)
155
- continue;
156
- const routeRegexes = [
157
- /(?:path|to|href|router\.push|navigate)\s*\(?\s*[:=]?\s*["'`]((?:\/(?!\/)[^"'`?#]+))["'`]/g,
158
- /<Route[^>]*path=["'`]((?:\/(?!\/)[^"'`?#]+))["'`]/g,
159
- ];
160
- for (const regex of routeRegexes) {
161
- for (const match of content.matchAll(regex)) {
162
- const route = normalizeRoute(match[1] ?? "");
163
- if (route)
164
- routes.set(route, filePath);
165
- }
166
- }
167
- }
168
- return Array.from(routes.entries())
169
- .map(([route, source]) => ({ route, source }))
170
- .sort((a, b) => a.route.localeCompare(b.route));
171
- }
172
- function buildSelectorHints(files) {
173
- const contents = files.map(readFileSafe).join("\n");
174
- const has = (pattern) => pattern.test(contents);
175
- const dataTestIds = Array.from(contents.matchAll(/data-testid=["'`]([^"'`]+)["'`]/g)).map((match) => match[1]);
176
- const dashboardDataTestId = dataTestIds.find((id) => /(dashboard|workspace|overview|app)/i.test(id));
177
- const authDataTestId = dataTestIds.find((id) => /(login|signin|auth|account)/i.test(id));
178
- return {
179
- email: has(/name=["'`]email["'`]/i)
180
- ? "input[name=email]"
181
- : has(/type=["'`]email["'`]/i)
182
- ? "input[type=email]"
183
- : "input[name=email]",
184
- password: has(/name=["'`](password|passcode)["'`]/i)
185
- ? "input[name=password]"
186
- : has(/type=["'`]password["'`]/i)
187
- ? "input[type=password]"
188
- : "input[name=password]",
189
- submit: has(/type=["'`]submit["'`]/i)
190
- ? "button[type=submit]"
191
- : has(/(sign in|login|continue|submit)/i)
192
- ? "button"
193
- : "button[type=submit]",
194
- search: has(/name=["'`](q|query|search)["'`]/i)
195
- ? "input[name=search]"
196
- : has(/placeholder=["'`][^"'`]*(search|검색)[^"'`]*["'`]/i)
197
- ? "input[type=search]"
198
- : "input[type=search]",
199
- primaryInput: has(/textarea/i) ? "textarea" : "input",
200
- dashboardVisible: dashboardDataTestId ? `[data-testid=${dashboardDataTestId}]` : "main",
201
- authVisible: authDataTestId ? `[data-testid=${authDataTestId}]` : "main",
202
- };
203
- }
204
- function pickFirstRoute(routes, patterns) {
205
- return routes.find((route) => patterns.some((pattern) => pattern.test(route)));
206
- }
207
- function uniqueRoutes(routes) {
208
- const seen = new Set();
209
- const result = [];
210
- for (const route of routes) {
211
- if (!route)
212
- continue;
213
- const normalized = normalizeRoute(route);
214
- if (!normalized || normalized === "/" || seen.has(normalized))
215
- continue;
216
- seen.add(normalized);
217
- result.push(normalized);
218
- }
219
- return result;
220
- }
221
- function analyzeProjectForInit(dir) {
222
- const files = collectSourceFiles(dir);
223
- const routeEntries = collectRoutesFromFiles(dir, files);
224
- const routes = uniqueRoutes(routeEntries.map((entry) => entry.route));
225
- const selectors = buildSelectorHints(files);
226
- const loginRoute = pickFirstRoute(routes, [/\/login$/, /\/signin$/, /\/sign-in$/, /\/auth/]);
227
- const dashboardRoute = pickFirstRoute(routes, [/\/dashboard$/, /\/app$/, /\/workspace$/, /\/overview$/, /\/home$/, /\/projects?$/]);
228
- const signupRoute = pickFirstRoute(routes, [/\/signup$/, /\/register$/, /\/join$/, /\/sign-up$/]);
229
- const searchRoute = pickFirstRoute(routes, [/\/search$/, /\/discover$/, /\/explore$/, /\/products$/, /\/items$/]);
230
- const settingsRoute = pickFirstRoute(routes, [/\/settings$/, /\/profile$/, /\/account$/, /\/billing$/]);
231
- const scenarios = [];
232
- if (loginRoute) {
233
- scenarios.push({
234
- name: "로그인 후 보호 페이지 접근",
235
- steps: [
236
- { goto: loginRoute },
237
- { fill: selectors.email, with: "test@example.com" },
238
- { fill: selectors.password, with: "testpass123!" },
239
- { click: selectors.submit },
240
- dashboardRoute
241
- ? { goto: dashboardRoute }
242
- : { wait: 1200 },
243
- { expect_visible: dashboardRoute ? selectors.dashboardVisible : "body" },
244
- ],
245
- });
246
- }
247
- if (signupRoute && scenarios.length < 3) {
248
- scenarios.push({
249
- name: "회원가입 주요 폼 제출",
250
- steps: [
251
- { goto: signupRoute },
252
- { fill: selectors.email, with: "test@example.com" },
253
- { fill: selectors.password, with: "testpass123!" },
254
- { click: selectors.submit },
255
- { expect_visible: selectors.authVisible || "body" },
256
- ],
257
- });
258
- }
259
- if (searchRoute && scenarios.length < 3) {
260
- scenarios.push({
261
- name: "핵심 탐색 흐름 확인",
262
- steps: [
263
- { goto: searchRoute },
264
- { fill: selectors.search, with: "test query" },
265
- { click: selectors.submit },
266
- { expect_visible: "body" },
267
- ],
268
- });
269
- }
270
- if (settingsRoute && scenarios.length < 3) {
271
- scenarios.push({
272
- name: "설정 화면 렌더 확인",
273
- steps: [
274
- { goto: settingsRoute },
275
- { expect_visible: "body" },
276
- ],
277
- });
278
- }
279
- if (dashboardRoute && scenarios.length < 3) {
280
- scenarios.push({
281
- name: "대시보드 기본 렌더",
282
- steps: [
283
- { goto: dashboardRoute },
284
- { expect_visible: selectors.dashboardVisible || "main" },
285
- ],
286
- });
287
- }
288
- if (scenarios.length === 0) {
289
- const firstRoute = routes[0] ?? "/";
290
- scenarios.push({
291
- name: "핵심 페이지 기본 렌더",
292
- steps: [
293
- { goto: firstRoute },
294
- { expect_visible: "body" },
295
- ],
296
- });
297
- }
298
- return {
299
- scenarios: scenarios.slice(0, 3),
300
- extraRoutes: routes.slice(0, 10),
301
- };
302
- }
@@ -1,7 +0,0 @@
1
- export interface RuntimeRouteDiscoveryResult {
2
- routes: string[];
3
- scriptUrls: string[];
4
- }
5
- export declare function extractScriptUrlsFromHtml(html: string, baseUrl: string): string[];
6
- export declare function extractRoutesFromText(content: string): string[];
7
- export declare function discoverRuntimeRoutes(baseUrl: string): Promise<RuntimeRouteDiscoveryResult>;
@@ -1,108 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.extractScriptUrlsFromHtml = extractScriptUrlsFromHtml;
4
- exports.extractRoutesFromText = extractRoutesFromText;
5
- exports.discoverRuntimeRoutes = discoverRuntimeRoutes;
6
- const SCRIPT_SRC_REGEX = /<script[^>]+src=["']([^"'#?]+(?:\?[^"'#]*)?)["']/gi;
7
- const HTML_ROUTE_REGEX = /(?:href|data-href)=["'](\/[^"'#? ]*)/gi;
8
- const ROUTE_SNIPPET_REGEXES = [
9
- /(?:path|pathname|href|to|route|router\.push|navigate)\s*[:=(]\s*["'`]((?:\/(?!\/)[^"'`?#]+))["'`]/g,
10
- /["'`]((?:\/(?!\/)[a-z0-9][^"'`?#]*))["'`]/gi,
11
- ];
12
- function normalizeRoute(candidate) {
13
- const decoded = candidate
14
- .replace(/\\u002F/gi, "/")
15
- .replace(/\\\//g, "/")
16
- .trim();
17
- if (!decoded.startsWith("/"))
18
- return null;
19
- const normalized = decoded
20
- .split("?")[0]
21
- ?.split("#")[0]
22
- ?.replace(/\/+/g, "/")
23
- ?.replace(/\/$/, "") || "/";
24
- if (normalized === "/" || normalized.length < 2)
25
- return null;
26
- if (normalized.startsWith("/_next/") || normalized.startsWith("/api/"))
27
- return null;
28
- if (/[.*:[\]]/.test(normalized))
29
- return null;
30
- if (/\.[a-z0-9]{2,8}$/i.test(normalized))
31
- return null;
32
- if (/\s/.test(normalized))
33
- return null;
34
- if (!/^\/[a-z0-9/_-]+$/i.test(normalized))
35
- return null;
36
- return normalized;
37
- }
38
- function extractScriptUrlsFromHtml(html, baseUrl) {
39
- const urls = [];
40
- const seen = new Set();
41
- for (const match of html.matchAll(SCRIPT_SRC_REGEX)) {
42
- const raw = match[1];
43
- if (!raw)
44
- continue;
45
- try {
46
- const scriptUrl = new URL(raw, baseUrl);
47
- if (scriptUrl.origin !== new URL(baseUrl).origin)
48
- continue;
49
- const href = scriptUrl.href;
50
- if (!seen.has(href)) {
51
- seen.add(href);
52
- urls.push(href);
53
- }
54
- }
55
- catch {
56
- // Ignore malformed URLs.
57
- }
58
- }
59
- return urls;
60
- }
61
- function extractRoutesFromText(content) {
62
- const routes = [];
63
- const seen = new Set();
64
- for (const regex of ROUTE_SNIPPET_REGEXES) {
65
- for (const match of content.matchAll(regex)) {
66
- const route = normalizeRoute(match[1] ?? "");
67
- if (!route || seen.has(route))
68
- continue;
69
- seen.add(route);
70
- routes.push(route);
71
- }
72
- }
73
- return routes;
74
- }
75
- async function discoverRuntimeRoutes(baseUrl) {
76
- const htmlRes = await fetch(baseUrl, {
77
- signal: AbortSignal.timeout(8000),
78
- headers: { accept: "text/html,application/xhtml+xml" },
79
- });
80
- const html = await htmlRes.text();
81
- const routes = new Set(extractRoutesFromText(html));
82
- for (const match of html.matchAll(HTML_ROUTE_REGEX)) {
83
- const route = normalizeRoute(match[1] ?? "");
84
- if (route)
85
- routes.add(route);
86
- }
87
- const scriptUrls = extractScriptUrlsFromHtml(html, baseUrl)
88
- .filter((url) => /\/_next\/static\/chunks\/|assets\/|static\/|build\//i.test(url))
89
- .slice(0, 10);
90
- await Promise.all(scriptUrls.map(async (scriptUrl) => {
91
- try {
92
- const res = await fetch(scriptUrl, { signal: AbortSignal.timeout(5000) });
93
- if (!res.ok)
94
- return;
95
- const content = await res.text();
96
- for (const route of extractRoutesFromText(content)) {
97
- routes.add(route);
98
- }
99
- }
100
- catch {
101
- // Skip chunk fetch failures. This is best-effort coverage expansion.
102
- }
103
- }));
104
- return {
105
- routes: Array.from(routes).sort((a, b) => a.localeCompare(b)),
106
- scriptUrls,
107
- };
108
- }