laxy-verify 0.1.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 ADDED
@@ -0,0 +1,163 @@
1
+ # laxy-verify
2
+
3
+ Frontend quality gate for any project. Checks your build and runs a Lighthouse audit — outputs a Silver/Bronze/Unverified grade.
4
+
5
+ ```bash
6
+ npx laxy-verify .
7
+ ```
8
+
9
+ ```
10
+ Laxy Verify — ✅ Silver
11
+ ========================================
12
+
13
+ Build: PASS (12.3s)
14
+
15
+ Lighthouse Scores:
16
+ Performance: 87 / 70 ✓
17
+ Accessibility: 92 / 85 ✓
18
+ SEO: 88 / 80 ✓
19
+ Best Practices: 90 / 80 ✓
20
+
21
+ Grade: Silver
22
+ ```
23
+
24
+ > Want Gold? Gold requires E2E tests on top of build + Lighthouse. Run full verification at [laxy.dev](https://laxy.dev).
25
+
26
+ ---
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ # Run once (no install needed)
32
+ npx laxy-verify .
33
+
34
+ # Or install globally
35
+ npm install -g laxy-verify
36
+ laxy-verify .
37
+ ```
38
+
39
+ ---
40
+
41
+ ## Usage
42
+
43
+ ```bash
44
+ laxy-verify [dir] [options]
45
+
46
+ Arguments:
47
+ dir Project directory (default: current directory)
48
+
49
+ Options:
50
+ --format <type> Output format: console, json, md (default: console)
51
+ --ci CI mode: relaxed thresholds, 3 Lighthouse runs
52
+ --skip-lighthouse Build check only, skip Lighthouse
53
+ --runs <number> Number of Lighthouse runs
54
+ --port <number> Dev server port (default: 3000)
55
+ ```
56
+
57
+ ### Examples
58
+
59
+ ```bash
60
+ # Check current directory
61
+ npx laxy-verify .
62
+
63
+ # Build check only (fast)
64
+ npx laxy-verify . --skip-lighthouse
65
+
66
+ # CI mode with JSON output
67
+ npx laxy-verify . --ci --format json
68
+
69
+ # Markdown output (for PR comments)
70
+ npx laxy-verify . --format md
71
+ ```
72
+
73
+ ---
74
+
75
+ ## Exit Codes
76
+
77
+ | Code | Meaning |
78
+ |------|---------|
79
+ | `0` | Silver or Gold — all checks passed |
80
+ | `1` | Bronze — build passed, Lighthouse failed (soft fail) |
81
+ | `2` | Unverified — build failed |
82
+ | `3` | Config error — no package.json, no build script |
83
+
84
+ ---
85
+
86
+ ## Grades
87
+
88
+ | Grade | Condition |
89
+ |-------|-----------|
90
+ | ✅ Silver | Build passed + all Lighthouse thresholds met |
91
+ | 🔨 Bronze | Build passed, Lighthouse failed or skipped |
92
+ | ⚠️ Unverified | Build failed |
93
+
94
+ > 🏆 **Gold** requires E2E tests. Use [Laxy](https://laxy.dev) for full Gold verification.
95
+
96
+ ---
97
+
98
+ ## GitHub Action
99
+
100
+ Add this to `.github/workflows/verify.yml`:
101
+
102
+ ```yaml
103
+ name: Laxy Verify
104
+
105
+ on:
106
+ pull_request:
107
+
108
+ jobs:
109
+ verify:
110
+ runs-on: ubuntu-latest
111
+ steps:
112
+ - uses: actions/checkout@v4
113
+ - uses: psungmin24/laxy-verify@v1
114
+ with:
115
+ format: md
116
+ ```
117
+
118
+ On every PR, laxy-verify will:
119
+ 1. Run `npm run build`
120
+ 2. Start your dev server and run Lighthouse
121
+ 3. Post a comment with the grade and scores
122
+
123
+ ### Action Inputs
124
+
125
+ | Input | Default | Description |
126
+ |-------|---------|-------------|
127
+ | `node-version` | `20` | Node.js version |
128
+ | `skip-lighthouse` | `false` | Build check only |
129
+ | `format` | `md` | Output format |
130
+
131
+ ---
132
+
133
+ ## Configuration
134
+
135
+ Create `.laxy.yml` in your project root to customize thresholds:
136
+
137
+ ```yaml
138
+ thresholds:
139
+ performance: 70
140
+ accessibility: 85
141
+ seo: 80
142
+ bestPractices: 80
143
+
144
+ port: 3000
145
+ runs: 1
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Supported Frameworks
151
+
152
+ Automatically detects and runs the right build command:
153
+
154
+ - **Next.js** — `next build`
155
+ - **Vite** — `vite build`
156
+ - **Create React App** — `react-scripts build`
157
+ - **Custom** — reads `scripts.build` from `package.json`
158
+
159
+ ---
160
+
161
+ ## License
162
+
163
+ MIT
package/action.yml ADDED
@@ -0,0 +1,97 @@
1
+ name: 'Laxy Verify'
2
+ description: 'Frontend quality gate: build check + Lighthouse audit + verification grade'
3
+ inputs:
4
+ threshold-config:
5
+ description: 'Path to .laxy.yml threshold config'
6
+ required: false
7
+ default: '.laxy.yml'
8
+ node-version:
9
+ description: 'Node.js version'
10
+ required: false
11
+ default: '20'
12
+ skip-lighthouse:
13
+ description: 'Skip Lighthouse, run build check only'
14
+ required: false
15
+ default: 'false'
16
+ outputs:
17
+ grade:
18
+ description: 'Verification grade (Gold/Silver/Bronze/Unverified)'
19
+ value: ${{ steps.verify.outputs.grade }}
20
+ report:
21
+ description: 'JSON report string'
22
+ value: ${{ steps.verify.outputs.report }}
23
+ runs:
24
+ using: 'composite'
25
+ steps:
26
+ - name: Setup Node.js
27
+ uses: actions/setup-node@v4
28
+ with:
29
+ node-version: ${{ inputs.node-version }}
30
+
31
+ - name: Install dependencies
32
+ shell: bash
33
+ run: npm ci
34
+
35
+ - name: Run Laxy Verify
36
+ id: verify
37
+ shell: bash
38
+ run: |
39
+ SKIP_FLAG=""
40
+ if [ "${{ inputs.skip-lighthouse }}" = "true" ]; then
41
+ SKIP_FLAG="--skip-lighthouse"
42
+ fi
43
+ npx laxy-verify . --format json --ci $SKIP_FLAG > laxy-report.json 2>&1 || true
44
+ GRADE=$(node -e "const r=JSON.parse(require('fs').readFileSync('laxy-report.json','utf8'));console.log(r.grade)")
45
+ echo "grade=$GRADE" >> $GITHUB_OUTPUT
46
+ echo "report=$(cat laxy-report.json)" >> $GITHUB_OUTPUT
47
+
48
+ - name: Post PR Comment
49
+ if: github.event_name == 'pull_request'
50
+ uses: actions/github-script@v7
51
+ with:
52
+ script: |
53
+ const fs = require('fs');
54
+ let report;
55
+ try {
56
+ report = JSON.parse(fs.readFileSync('laxy-report.json', 'utf8'));
57
+ } catch {
58
+ console.log('No report file found');
59
+ return;
60
+ }
61
+
62
+ // Generate markdown from report
63
+ const npx = process.platform === 'win32' ? 'npx.cmd' : 'npx';
64
+ const { execSync } = require('child_process');
65
+ let md;
66
+ try {
67
+ md = execSync(`${npx} laxy-verify . --format md --ci --skip-lighthouse 2>/dev/null || true`).toString();
68
+ } catch {
69
+ md = `## Laxy Verify — ${report.grade}\n\nSee JSON report for details.`;
70
+ }
71
+
72
+ // Find existing comment
73
+ const marker = '<!-- laxy-verify -->';
74
+ const { data: comments } = await github.rest.issues.listComments({
75
+ owner: context.repo.owner,
76
+ repo: context.repo.repo,
77
+ issue_number: context.issue.number,
78
+ });
79
+ const existing = comments.find(c => c.body?.includes(marker));
80
+
81
+ const body = md.includes(marker) ? md : `${marker}\n${md}`;
82
+
83
+ if (existing) {
84
+ await github.rest.issues.updateComment({
85
+ owner: context.repo.owner,
86
+ repo: context.repo.repo,
87
+ comment_id: existing.id,
88
+ body,
89
+ });
90
+ } else {
91
+ await github.rest.issues.createComment({
92
+ owner: context.repo.owner,
93
+ repo: context.repo.repo,
94
+ issue_number: context.issue.number,
95
+ body,
96
+ });
97
+ }
@@ -0,0 +1,22 @@
1
+ export interface BuildError {
2
+ file?: string;
3
+ line?: number;
4
+ col?: number;
5
+ code?: string;
6
+ message: string;
7
+ raw: string;
8
+ }
9
+ export interface BuildCheckResult {
10
+ success: boolean;
11
+ errors: string[];
12
+ structuredErrors: BuildError[];
13
+ warnings: string[];
14
+ duration: number;
15
+ output: string;
16
+ }
17
+ export declare function parseBuildOutput(output: string): {
18
+ errors: string[];
19
+ structuredErrors: BuildError[];
20
+ warnings: string[];
21
+ };
22
+ export declare function runBuild(projectPath: string): Promise<BuildCheckResult>;
@@ -0,0 +1,156 @@
1
+ import { spawn } from "child_process";
2
+ import { readFileSync, existsSync, readdirSync } from "fs";
3
+ import { join } from "path";
4
+ import { detectRuntimeFromFiles, getBuildCommandCandidates, parseProjectPackageJson, } from "./project-runtime.js";
5
+ function parseErrorLine(raw) {
6
+ const clean = raw.trim();
7
+ if (!clean)
8
+ return null;
9
+ const tsMatch = clean.match(/^(.+?)\((\d+),(\d+)\):\s+error\s+(TS\d+):\s+(.+)$/);
10
+ if (tsMatch) {
11
+ return {
12
+ file: tsMatch[1].trim(),
13
+ line: parseInt(tsMatch[2], 10),
14
+ col: parseInt(tsMatch[3], 10),
15
+ code: tsMatch[4],
16
+ message: tsMatch[5].slice(0, 200),
17
+ raw: clean.slice(0, 250),
18
+ };
19
+ }
20
+ if (/error TS\d+:|Module not found|Cannot find module|Failed to resolve|SyntaxError:|Unexpected token|Build error occurred|Failed to compile/i.test(clean)) {
21
+ return { message: clean.slice(0, 200), raw: clean.slice(0, 250) };
22
+ }
23
+ return null;
24
+ }
25
+ export function parseBuildOutput(output) {
26
+ const errors = [];
27
+ const structuredErrors = [];
28
+ const warnings = [];
29
+ for (const line of output.split("\n")) {
30
+ const clean = line.trim();
31
+ if (!clean)
32
+ continue;
33
+ const structured = parseErrorLine(clean);
34
+ if (structured) {
35
+ errors.push(structured.raw);
36
+ structuredErrors.push(structured);
37
+ }
38
+ else if (/^warn|warning:/i.test(clean)) {
39
+ warnings.push(clean.slice(0, 150));
40
+ }
41
+ }
42
+ return {
43
+ errors: errors.slice(0, 20),
44
+ structuredErrors: structuredErrors.slice(0, 20),
45
+ warnings: warnings.slice(0, 10),
46
+ };
47
+ }
48
+ function extractFallbackBuildErrors(output, exitCode) {
49
+ const meaningfulLines = output
50
+ .split("\n")
51
+ .map((l) => l.trim())
52
+ .filter(Boolean)
53
+ .filter((l) => !/^warn|warning:|^> |^npm notice/i.test(l));
54
+ const likelyErrorLines = meaningfulLines.filter((l) => /error|failed|unable|cannot|missing|unexpected|resolve|exception|ELIFECYCLE|ERR!/i.test(l));
55
+ const selected = (likelyErrorLines.length > 0 ? likelyErrorLines : meaningfulLines)
56
+ .slice(-5)
57
+ .map((l) => l.slice(0, 200));
58
+ if (selected.length > 0)
59
+ return Array.from(new Set(selected));
60
+ return [`Build failed with exit code ${exitCode ?? "unknown"} but produced no parsed diagnostics.`];
61
+ }
62
+ function runBuildCommand(command, projectPath) {
63
+ const startTime = Date.now();
64
+ return new Promise((resolve) => {
65
+ const cmd = process.platform === "win32" ? `chcp 65001 > nul && ${command.cmd}` : command.cmd;
66
+ const child = spawn(cmd, command.args, {
67
+ cwd: projectPath,
68
+ shell: true,
69
+ env: { ...process.env, CI: "true", NODE_ENV: "production" },
70
+ });
71
+ let output = "";
72
+ child.stdout?.on("data", (d) => { output += d.toString(); });
73
+ child.stderr?.on("data", (d) => { output += d.toString(); });
74
+ const timeout = setTimeout(() => {
75
+ child.kill();
76
+ resolve({
77
+ success: false,
78
+ errors: ["Build timed out after 90s"],
79
+ structuredErrors: [{ message: "Build timed out after 90s", raw: "Build timed out after 90s" }],
80
+ warnings: [],
81
+ duration: 90000,
82
+ output: output.slice(-1000),
83
+ });
84
+ }, 90000);
85
+ child.on("close", (code) => {
86
+ clearTimeout(timeout);
87
+ const duration = Date.now() - startTime;
88
+ let { errors, structuredErrors, warnings } = parseBuildOutput(output);
89
+ if (code !== 0 && errors.length === 0) {
90
+ const fallback = extractFallbackBuildErrors(output, code);
91
+ errors = fallback;
92
+ structuredErrors = fallback.map((raw) => ({ message: raw.slice(0, 200), raw }));
93
+ }
94
+ resolve({ success: code === 0, errors, structuredErrors, warnings, duration, output: output.slice(-2000) });
95
+ });
96
+ child.on("error", (err) => {
97
+ clearTimeout(timeout);
98
+ resolve({
99
+ success: false,
100
+ errors: [`Build process error: ${err.message}`],
101
+ structuredErrors: [{ message: `Build process error: ${err.message}`, raw: err.message }],
102
+ warnings: [],
103
+ duration: Date.now() - startTime,
104
+ output: "",
105
+ });
106
+ });
107
+ });
108
+ }
109
+ export async function runBuild(projectPath) {
110
+ const packageJsonPath = join(projectPath, "package.json");
111
+ if (!existsSync(packageJsonPath)) {
112
+ return {
113
+ success: false,
114
+ errors: ["Not a Node.js project: package.json not found"],
115
+ structuredErrors: [{ message: "Not a Node.js project", raw: "package.json not found" }],
116
+ warnings: [],
117
+ duration: 0,
118
+ output: "",
119
+ };
120
+ }
121
+ const pkgContent = readFileSync(packageJsonPath, "utf-8");
122
+ const packageJson = parseProjectPackageJson(pkgContent);
123
+ const candidateFiles = readdirSync(projectPath).filter((f) => !f.startsWith("."));
124
+ const runtime = detectRuntimeFromFiles(candidateFiles, packageJson);
125
+ const npm = process.platform === "win32" ? "npm.cmd" : "npm";
126
+ const npx = process.platform === "win32" ? "npx.cmd" : "npx";
127
+ const buildCommands = getBuildCommandCandidates(runtime, { npmCommand: npm, npxCommand: npx });
128
+ if (buildCommands.length === 0) {
129
+ return {
130
+ success: false,
131
+ errors: ["No build command found in package.json"],
132
+ structuredErrors: [{ message: "No build command found", raw: "No build script in package.json" }],
133
+ warnings: [],
134
+ duration: 0,
135
+ output: "",
136
+ };
137
+ }
138
+ for (const command of buildCommands) {
139
+ const result = await runBuildCommand(command, projectPath);
140
+ if (result.success)
141
+ return result;
142
+ // If this wasn't a "command not found" error, stop trying
143
+ if (!/missing script|command not found|is not recognized/i.test(result.output)) {
144
+ return result;
145
+ }
146
+ }
147
+ return {
148
+ success: false,
149
+ errors: ["All build commands failed"],
150
+ structuredErrors: [{ message: "All build commands failed", raw: "No build command succeeded" }],
151
+ warnings: [],
152
+ duration: 0,
153
+ output: "",
154
+ };
155
+ }
156
+ //# sourceMappingURL=build-runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-runner.js","sourceRoot":"","sources":["../src/build-runner.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { resolve } from "path";
4
+ import { existsSync } from "fs";
5
+ import { loadConfig } from "./config.js";
6
+ import { runBuild } from "./build-runner.js";
7
+ import { runLighthouse } from "./lighthouse-runner.js";
8
+ import { getVerificationGrade, getImprovementRecommendations } from "./verification.js";
9
+ import { formatReport } from "./reporter.js";
10
+ const EXIT_PASS = 0; // Silver or Gold
11
+ const EXIT_SOFT_FAIL = 1; // Bronze (build OK, LH failed)
12
+ const EXIT_FAIL = 2; // Unverified (build failed)
13
+ const EXIT_CONFIG = 3; // Config error
14
+ const program = new Command();
15
+ program
16
+ .name("laxy-verify")
17
+ .description("Frontend quality gate: build check + Lighthouse audit + verification grade")
18
+ .version("0.1.0")
19
+ .argument("[dir]", "Project directory", ".")
20
+ .option("--format <type>", "Output format: console, json, md", "console")
21
+ .option("--ci", "CI mode: relaxed thresholds, 3 Lighthouse runs")
22
+ .option("--skip-lighthouse", "Skip Lighthouse, build-only verification")
23
+ .option("--runs <number>", "Number of Lighthouse runs", parseInt)
24
+ .option("--port <number>", "Dev server port", parseInt)
25
+ .action(async (dir, opts) => {
26
+ const projectPath = resolve(dir);
27
+ if (!existsSync(projectPath)) {
28
+ console.error(`Directory not found: ${projectPath}`);
29
+ process.exit(EXIT_CONFIG);
30
+ }
31
+ if (!existsSync(resolve(projectPath, "package.json"))) {
32
+ console.error("Not a Node.js project: package.json not found");
33
+ process.exit(EXIT_CONFIG);
34
+ }
35
+ // Load config
36
+ const { config, warnings } = loadConfig(projectPath, opts.ci);
37
+ for (const w of warnings)
38
+ console.warn(`[warn] ${w}`);
39
+ // Override config with CLI flags
40
+ if (opts.runs)
41
+ config.runs = opts.runs;
42
+ if (opts.port)
43
+ config.port = opts.port;
44
+ // Step 1: Build check
45
+ const buildResult = await runBuild(projectPath);
46
+ if (!buildResult.success) {
47
+ const report = {
48
+ grade: "unverified",
49
+ build: { success: false, errors: buildResult.errors, duration: buildResult.duration },
50
+ lighthouse: { scores: null },
51
+ thresholds: config.thresholds,
52
+ recommendations: getImprovementRecommendations({
53
+ buildSuccess: false,
54
+ buildErrors: buildResult.errors,
55
+ }),
56
+ };
57
+ console.log(formatReport(report, opts.format));
58
+ process.exit(EXIT_FAIL);
59
+ }
60
+ // Step 2: Lighthouse (optional)
61
+ let lighthouseScores = null;
62
+ let lighthouseError;
63
+ if (!opts.skipLighthouse) {
64
+ const lhResult = await runLighthouse(projectPath, {
65
+ port: config.port,
66
+ runs: config.runs,
67
+ ciMode: opts.ci,
68
+ });
69
+ lighthouseScores = lhResult.scores;
70
+ lighthouseError = lhResult.error;
71
+ }
72
+ // Step 3: Grade
73
+ const grade = getVerificationGrade({
74
+ buildSuccess: true,
75
+ lighthouseScores: lighthouseScores ?? undefined,
76
+ });
77
+ const report = {
78
+ grade,
79
+ build: { success: true, errors: [], duration: buildResult.duration },
80
+ lighthouse: { scores: lighthouseScores, error: lighthouseError },
81
+ thresholds: config.thresholds,
82
+ recommendations: getImprovementRecommendations({
83
+ buildSuccess: true,
84
+ lighthouseScores: lighthouseScores ?? undefined,
85
+ }),
86
+ };
87
+ console.log(formatReport(report, opts.format));
88
+ // Exit codes
89
+ switch (grade) {
90
+ case "gold":
91
+ case "silver":
92
+ process.exit(EXIT_PASS);
93
+ break;
94
+ case "bronze":
95
+ process.exit(EXIT_SOFT_FAIL);
96
+ break;
97
+ default:
98
+ process.exit(EXIT_FAIL);
99
+ }
100
+ });
101
+ program.parse();
102
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ export interface LaxyConfig {
2
+ thresholds: {
3
+ performance: number;
4
+ accessibility: number;
5
+ seo: number;
6
+ bestPractices: number;
7
+ };
8
+ buildCommand?: string;
9
+ devCommand?: string;
10
+ port: number;
11
+ runs: number;
12
+ }
13
+ export declare function loadConfig(projectPath: string, ciMode?: boolean): {
14
+ config: LaxyConfig;
15
+ warnings: string[];
16
+ };
package/dist/config.js ADDED
@@ -0,0 +1,55 @@
1
+ import { readFileSync, existsSync } from "fs";
2
+ import { join } from "path";
3
+ import { parse as parseYaml } from "yaml";
4
+ import { LH_THRESHOLDS, LH_CI_THRESHOLDS } from "./verification.js";
5
+ const DEFAULT_CONFIG = {
6
+ thresholds: { ...LH_THRESHOLDS },
7
+ port: 3000,
8
+ runs: 1,
9
+ };
10
+ export function loadConfig(projectPath, ciMode = false) {
11
+ const warnings = [];
12
+ const configPath = join(projectPath, ".laxy.yml");
13
+ const defaults = {
14
+ thresholds: ciMode ? { ...LH_CI_THRESHOLDS } : { ...LH_THRESHOLDS },
15
+ port: DEFAULT_CONFIG.port,
16
+ runs: ciMode ? 3 : 1,
17
+ };
18
+ if (!existsSync(configPath)) {
19
+ return { config: defaults, warnings };
20
+ }
21
+ try {
22
+ const raw = readFileSync(configPath, "utf-8");
23
+ const parsed = parseYaml(raw);
24
+ if (!parsed || typeof parsed !== "object") {
25
+ warnings.push("Invalid .laxy.yml: not an object. Using defaults.");
26
+ return { config: defaults, warnings };
27
+ }
28
+ const config = { ...defaults };
29
+ if (parsed.thresholds && typeof parsed.thresholds === "object") {
30
+ const t = parsed.thresholds;
31
+ if (typeof t.performance === "number")
32
+ config.thresholds.performance = t.performance;
33
+ if (typeof t.accessibility === "number")
34
+ config.thresholds.accessibility = t.accessibility;
35
+ if (typeof t.seo === "number")
36
+ config.thresholds.seo = t.seo;
37
+ if (typeof t.bestPractices === "number")
38
+ config.thresholds.bestPractices = t.bestPractices;
39
+ }
40
+ if (typeof parsed.buildCommand === "string")
41
+ config.buildCommand = parsed.buildCommand;
42
+ if (typeof parsed.devCommand === "string")
43
+ config.devCommand = parsed.devCommand;
44
+ if (typeof parsed.port === "number")
45
+ config.port = parsed.port;
46
+ if (typeof parsed.runs === "number")
47
+ config.runs = Math.max(1, Math.min(parsed.runs, 5));
48
+ return { config, warnings };
49
+ }
50
+ catch {
51
+ warnings.push("Failed to parse .laxy.yml. Using defaults.");
52
+ return { config: defaults, warnings };
53
+ }
54
+ }
55
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAepE,MAAM,cAAc,GAAe;IACjC,UAAU,EAAE,EAAE,GAAG,aAAa,EAAE;IAChC,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,CAAC;CACR,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,WAAmB,EAAE,MAAM,GAAG,KAAK;IAC5D,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;IAElD,MAAM,QAAQ,GAAe;QAC3B,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,aAAa,EAAE;QACnE,IAAI,EAAE,cAAc,CAAC,IAAI;QACzB,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KACrB,CAAC;IAEF,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACxC,CAAC;IAED,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QAE9B,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC1C,QAAQ,CAAC,IAAI,CAAC,mDAAmD,CAAC,CAAC;YACnE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;QACxC,CAAC;QAED,MAAM,MAAM,GAAe,EAAE,GAAG,QAAQ,EAAE,CAAC;QAE3C,IAAI,MAAM,CAAC,UAAU,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,EAAE,CAAC;YAC/D,MAAM,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC;YAC5B,IAAI,OAAO,CAAC,CAAC,WAAW,KAAK,QAAQ;gBAAE,MAAM,CAAC,UAAU,CAAC,WAAW,GAAG,CAAC,CAAC,WAAW,CAAC;YACrF,IAAI,OAAO,CAAC,CAAC,aAAa,KAAK,QAAQ;gBAAE,MAAM,CAAC,UAAU,CAAC,aAAa,GAAG,CAAC,CAAC,aAAa,CAAC;YAC3F,IAAI,OAAO,CAAC,CAAC,GAAG,KAAK,QAAQ;gBAAE,MAAM,CAAC,UAAU,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC;YAC7D,IAAI,OAAO,CAAC,CAAC,aAAa,KAAK,QAAQ;gBAAE,MAAM,CAAC,UAAU,CAAC,aAAa,GAAG,CAAC,CAAC,aAAa,CAAC;QAC7F,CAAC;QAED,IAAI,OAAO,MAAM,CAAC,YAAY,KAAK,QAAQ;YAAE,MAAM,CAAC,YAAY,GAAG,MAAM,CAAC,YAAY,CAAC;QACvF,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ;YAAE,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;QACjF,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;QAC/D,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAEzF,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,QAAQ,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;QAC5D,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACxC,CAAC;AACH,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { type ChildProcess } from "child_process";
2
+ export interface DevServerHandle {
3
+ process: ChildProcess;
4
+ port: number;
5
+ url: string;
6
+ }
7
+ export declare function startDevServer(projectPath: string, port: number): Promise<DevServerHandle>;
8
+ export declare function stopDevServer(handle: DevServerHandle): void;