mirror of
https://github.com/Egonex-AI/Understand-Anything.git
synced 2026-06-22 10:58:03 +08:00
feat(core): add 12 custom parsers for non-code file types
Add regex/parser-based analyzers for: Markdown, YAML, JSON, TOML, Env, Dockerfile, SQL, GraphQL, Protobuf, Terraform, Makefile, Shell. Each implements AnalyzerPlugin with analyzeFile() and optional extractReferences(). Uses `yaml` npm package for YAML parsing, built-in JSON.parse for JSON, regex for all others. Add registerAllParsers() helper to register all parsers at once. Add comprehensive test suite with 35 tests covering all parsers. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,7 @@
|
||||
"tree-sitter-javascript": "^0.25.0",
|
||||
"tree-sitter-typescript": "^0.23.2",
|
||||
"web-tree-sitter": "^0.26.6",
|
||||
"yaml": "^2.8.3",
|
||||
"zod": "^4.3.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,386 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { MarkdownParser } from "../plugins/parsers/markdown-parser.js";
|
||||
import { YAMLConfigParser } from "../plugins/parsers/yaml-parser.js";
|
||||
import { JSONConfigParser } from "../plugins/parsers/json-parser.js";
|
||||
import { TOMLParser } from "../plugins/parsers/toml-parser.js";
|
||||
import { EnvParser } from "../plugins/parsers/env-parser.js";
|
||||
import { DockerfileParser } from "../plugins/parsers/dockerfile-parser.js";
|
||||
import { SQLParser } from "../plugins/parsers/sql-parser.js";
|
||||
import { GraphQLParser } from "../plugins/parsers/graphql-parser.js";
|
||||
import { ProtobufParser } from "../plugins/parsers/protobuf-parser.js";
|
||||
import { TerraformParser } from "../plugins/parsers/terraform-parser.js";
|
||||
import { MakefileParser } from "../plugins/parsers/makefile-parser.js";
|
||||
import { ShellParser } from "../plugins/parsers/shell-parser.js";
|
||||
import { registerAllParsers } from "../plugins/parsers/index.js";
|
||||
import { PluginRegistry } from "../plugins/registry.js";
|
||||
|
||||
describe("MarkdownParser", () => {
|
||||
const parser = new MarkdownParser();
|
||||
|
||||
it("extracts heading sections", () => {
|
||||
const content = "# Title\n\nIntro\n\n## Section A\n\nContent A\n\n### Subsection\n\nContent B";
|
||||
const result = parser.analyzeFile("README.md", content);
|
||||
expect(result.sections).toHaveLength(3);
|
||||
expect(result.sections![0]).toMatchObject({ name: "Title", level: 1 });
|
||||
expect(result.sections![1]).toMatchObject({ name: "Section A", level: 2 });
|
||||
expect(result.sections![2]).toMatchObject({ name: "Subsection", level: 3 });
|
||||
});
|
||||
|
||||
it("extracts YAML front matter as imports", () => {
|
||||
const content = "---\ntitle: Test\ntags: [a, b]\n---\n# Content";
|
||||
const result = parser.analyzeFile("post.md", content);
|
||||
expect(result.imports).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("extracts file references", () => {
|
||||
const content = "See [guide](./docs/guide.md) and ";
|
||||
const refs = parser.extractReferences!("README.md", content);
|
||||
expect(refs).toHaveLength(2);
|
||||
expect(refs[0]).toMatchObject({ target: "./docs/guide.md", referenceType: "file" });
|
||||
expect(refs[1]).toMatchObject({ target: "./assets/logo.png", referenceType: "image" });
|
||||
});
|
||||
|
||||
it("skips external URLs in references", () => {
|
||||
const content = "[link](https://example.com) and [local](./file.md)";
|
||||
const refs = parser.extractReferences!("README.md", content);
|
||||
expect(refs).toHaveLength(1);
|
||||
expect(refs[0].target).toBe("./file.md");
|
||||
});
|
||||
|
||||
it("returns empty sections for empty content", () => {
|
||||
const result = parser.analyzeFile("empty.md", "");
|
||||
expect(result.sections).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("YAMLConfigParser", () => {
|
||||
const parser = new YAMLConfigParser();
|
||||
|
||||
it("extracts top-level key sections", () => {
|
||||
const content = "name: my-app\nversion: 1.0\nservices:\n web:\n image: node\n db:\n image: postgres";
|
||||
const result = parser.analyzeFile("config.yaml", content);
|
||||
expect(result.sections).toBeDefined();
|
||||
expect(result.sections!.length).toBeGreaterThanOrEqual(3);
|
||||
expect(result.sections!.map(s => s.name)).toContain("name");
|
||||
expect(result.sections!.map(s => s.name)).toContain("services");
|
||||
});
|
||||
|
||||
it("handles invalid YAML gracefully", () => {
|
||||
const content = "invalid: yaml: content: [[[";
|
||||
const result = parser.analyzeFile("broken.yaml", content);
|
||||
expect(result.sections).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("JSONConfigParser", () => {
|
||||
const parser = new JSONConfigParser();
|
||||
|
||||
it("extracts top-level key sections", () => {
|
||||
const content = '{\n "name": "my-app",\n "version": "1.0",\n "dependencies": {}\n}';
|
||||
const result = parser.analyzeFile("package.json", content);
|
||||
expect(result.sections).toBeDefined();
|
||||
expect(result.sections!.map(s => s.name)).toContain("name");
|
||||
expect(result.sections!.map(s => s.name)).toContain("dependencies");
|
||||
});
|
||||
|
||||
it("extracts $ref references", () => {
|
||||
const content = '{\n "$ref": "./common.json#/defs/User"\n}';
|
||||
const refs = parser.extractReferences!("schema.json", content);
|
||||
expect(refs).toHaveLength(1);
|
||||
expect(refs[0]).toMatchObject({ target: "./common.json#/defs/User", referenceType: "schema" });
|
||||
});
|
||||
|
||||
it("skips internal $ref references", () => {
|
||||
const content = '{\n "$ref": "#/definitions/User"\n}';
|
||||
const refs = parser.extractReferences!("schema.json", content);
|
||||
expect(refs).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("handles invalid JSON gracefully", () => {
|
||||
const content = "not json at all";
|
||||
const result = parser.analyzeFile("broken.json", content);
|
||||
expect(result.sections).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("TOMLParser", () => {
|
||||
const parser = new TOMLParser();
|
||||
|
||||
it("extracts section headers", () => {
|
||||
const content = "[package]\nname = \"my-app\"\n\n[dependencies]\nfoo = \"1.0\"\n\n[[bin]]\nname = \"cli\"";
|
||||
const result = parser.analyzeFile("Cargo.toml", content);
|
||||
expect(result.sections).toBeDefined();
|
||||
expect(result.sections!.length).toBe(3);
|
||||
expect(result.sections![0].name).toBe("package");
|
||||
expect(result.sections![1].name).toBe("dependencies");
|
||||
expect(result.sections![2].name).toBe("[[bin]]");
|
||||
});
|
||||
});
|
||||
|
||||
describe("EnvParser", () => {
|
||||
const parser = new EnvParser();
|
||||
|
||||
it("extracts variable names", () => {
|
||||
const content = "# Database config\nDB_HOST=localhost\nDB_PORT=5432\n\n# API\nAPI_KEY=secret123";
|
||||
const result = parser.analyzeFile(".env", content);
|
||||
expect(result.definitions).toBeDefined();
|
||||
expect(result.definitions!).toHaveLength(3);
|
||||
expect(result.definitions!.map(d => d.name)).toEqual(["DB_HOST", "DB_PORT", "API_KEY"]);
|
||||
});
|
||||
|
||||
it("skips comments and empty lines", () => {
|
||||
const content = "# comment\n\nVAR=value";
|
||||
const result = parser.analyzeFile(".env", content);
|
||||
expect(result.definitions!).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("DockerfileParser", () => {
|
||||
const parser = new DockerfileParser();
|
||||
|
||||
it("extracts FROM stages", () => {
|
||||
const content = "FROM node:22-slim AS builder\nRUN npm install\n\nFROM node:22-slim AS runner\nCOPY --from=builder /app /app\nEXPOSE 3000";
|
||||
const result = parser.analyzeFile("Dockerfile", content);
|
||||
expect(result.services).toBeDefined();
|
||||
expect(result.services!).toHaveLength(2);
|
||||
expect(result.services![0]).toMatchObject({ name: "builder", image: "node:22-slim" });
|
||||
expect(result.services![1]).toMatchObject({ name: "runner", image: "node:22-slim" });
|
||||
});
|
||||
|
||||
it("extracts EXPOSE ports", () => {
|
||||
const content = "FROM node:22\nEXPOSE 3000 8080\nCMD [\"node\", \"server.js\"]";
|
||||
const result = parser.analyzeFile("Dockerfile", content);
|
||||
expect(result.services![0].ports).toContain(3000);
|
||||
expect(result.services![0].ports).toContain(8080);
|
||||
});
|
||||
|
||||
it("extracts steps", () => {
|
||||
const content = "FROM node:22\nWORKDIR /app\nCOPY . .\nRUN npm install\nCMD [\"node\", \"start\"]";
|
||||
const result = parser.analyzeFile("Dockerfile", content);
|
||||
expect(result.steps).toBeDefined();
|
||||
expect(result.steps!.length).toBe(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe("SQLParser", () => {
|
||||
const parser = new SQLParser();
|
||||
|
||||
it("extracts CREATE TABLE definitions with columns", () => {
|
||||
const content = `CREATE TABLE users (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT UNIQUE
|
||||
);
|
||||
|
||||
CREATE TABLE posts (
|
||||
id INTEGER PRIMARY KEY,
|
||||
user_id INTEGER,
|
||||
title TEXT,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
);`;
|
||||
const result = parser.analyzeFile("schema.sql", content);
|
||||
expect(result.definitions).toBeDefined();
|
||||
expect(result.definitions!).toHaveLength(2);
|
||||
expect(result.definitions![0]).toMatchObject({ name: "users", kind: "table" });
|
||||
expect(result.definitions![0].fields).toContain("id");
|
||||
expect(result.definitions![0].fields).toContain("name");
|
||||
expect(result.definitions![0].fields).toContain("email");
|
||||
expect(result.definitions![1]).toMatchObject({ name: "posts", kind: "table" });
|
||||
});
|
||||
|
||||
it("extracts CREATE VIEW", () => {
|
||||
const content = "CREATE VIEW active_users AS SELECT * FROM users WHERE active = true;";
|
||||
const result = parser.analyzeFile("views.sql", content);
|
||||
expect(result.definitions!.some(d => d.name === "active_users" && d.kind === "view")).toBe(true);
|
||||
});
|
||||
|
||||
it("extracts CREATE INDEX", () => {
|
||||
const content = "CREATE UNIQUE INDEX idx_users_email ON users(email);";
|
||||
const result = parser.analyzeFile("indexes.sql", content);
|
||||
expect(result.definitions!.some(d => d.name === "idx_users_email" && d.kind === "index")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("GraphQLParser", () => {
|
||||
const parser = new GraphQLParser();
|
||||
|
||||
it("extracts type definitions", () => {
|
||||
const content = `type User {
|
||||
id: ID!
|
||||
name: String!
|
||||
email: String!
|
||||
}
|
||||
|
||||
type Post {
|
||||
id: ID!
|
||||
title: String!
|
||||
author: User!
|
||||
}`;
|
||||
const result = parser.analyzeFile("schema.graphql", content);
|
||||
expect(result.definitions).toBeDefined();
|
||||
expect(result.definitions!).toHaveLength(2);
|
||||
expect(result.definitions![0]).toMatchObject({ name: "User", kind: "type" });
|
||||
expect(result.definitions![0].fields).toContain("id");
|
||||
expect(result.definitions![0].fields).toContain("name");
|
||||
expect(result.definitions![1]).toMatchObject({ name: "Post", kind: "type" });
|
||||
});
|
||||
|
||||
it("extracts Query/Mutation endpoints", () => {
|
||||
const content = `type Query {
|
||||
users: [User!]!
|
||||
user(id: ID!): User
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
createUser(name: String!): User!
|
||||
}`;
|
||||
const result = parser.analyzeFile("schema.graphql", content);
|
||||
expect(result.endpoints).toBeDefined();
|
||||
expect(result.endpoints!.length).toBeGreaterThanOrEqual(3);
|
||||
expect(result.endpoints!.some(e => e.method === "Query" && e.path === "users")).toBe(true);
|
||||
expect(result.endpoints!.some(e => e.method === "Mutation" && e.path === "createUser")).toBe(true);
|
||||
});
|
||||
|
||||
it("extracts enum definitions", () => {
|
||||
const content = "enum Role {\n ADMIN\n USER\n GUEST\n}";
|
||||
const result = parser.analyzeFile("schema.graphql", content);
|
||||
expect(result.definitions!.some(d => d.name === "Role" && d.kind === "enum")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ProtobufParser", () => {
|
||||
const parser = new ProtobufParser();
|
||||
|
||||
it("extracts message definitions with fields", () => {
|
||||
const content = `message User {
|
||||
string name = 1;
|
||||
int32 age = 2;
|
||||
repeated string emails = 3;
|
||||
}`;
|
||||
const result = parser.analyzeFile("user.proto", content);
|
||||
expect(result.definitions).toBeDefined();
|
||||
expect(result.definitions!).toHaveLength(1);
|
||||
expect(result.definitions![0]).toMatchObject({ name: "User", kind: "message" });
|
||||
expect(result.definitions![0].fields).toContain("name");
|
||||
expect(result.definitions![0].fields).toContain("age");
|
||||
expect(result.definitions![0].fields).toContain("emails");
|
||||
});
|
||||
|
||||
it("extracts enum definitions", () => {
|
||||
const content = "enum Status {\n UNKNOWN = 0;\n ACTIVE = 1;\n INACTIVE = 2;\n}";
|
||||
const result = parser.analyzeFile("status.proto", content);
|
||||
expect(result.definitions!.some(d => d.name === "Status" && d.kind === "enum")).toBe(true);
|
||||
expect(result.definitions![0].fields).toContain("UNKNOWN");
|
||||
expect(result.definitions![0].fields).toContain("ACTIVE");
|
||||
});
|
||||
|
||||
it("extracts service RPC methods", () => {
|
||||
const content = `service UserService {
|
||||
rpc GetUser (GetUserRequest) returns (User);
|
||||
rpc CreateUser (CreateUserRequest) returns (User);
|
||||
}`;
|
||||
const result = parser.analyzeFile("service.proto", content);
|
||||
expect(result.endpoints).toBeDefined();
|
||||
expect(result.endpoints!).toHaveLength(2);
|
||||
expect(result.endpoints![0]).toMatchObject({ method: "rpc", path: "UserService.GetUser" });
|
||||
expect(result.endpoints![1]).toMatchObject({ method: "rpc", path: "UserService.CreateUser" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("TerraformParser", () => {
|
||||
const parser = new TerraformParser();
|
||||
|
||||
it("extracts resource blocks", () => {
|
||||
const content = `resource "aws_s3_bucket" "main" {
|
||||
bucket = "my-bucket"
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "lambda" {
|
||||
name = "lambda-role"
|
||||
}`;
|
||||
const result = parser.analyzeFile("main.tf", content);
|
||||
expect(result.resources).toBeDefined();
|
||||
expect(result.resources!).toHaveLength(2);
|
||||
expect(result.resources![0]).toMatchObject({ name: "aws_s3_bucket.main", kind: "aws_s3_bucket" });
|
||||
expect(result.resources![1]).toMatchObject({ name: "aws_iam_role.lambda", kind: "aws_iam_role" });
|
||||
});
|
||||
|
||||
it("extracts data blocks", () => {
|
||||
const content = 'data "aws_ami" "ubuntu" {\n most_recent = true\n}';
|
||||
const result = parser.analyzeFile("data.tf", content);
|
||||
expect(result.resources!.some(r => r.name === "data.aws_ami.ubuntu")).toBe(true);
|
||||
});
|
||||
|
||||
it("extracts module blocks", () => {
|
||||
const content = 'module "vpc" {\n source = "./modules/vpc"\n}';
|
||||
const result = parser.analyzeFile("modules.tf", content);
|
||||
expect(result.resources!.some(r => r.name === "module.vpc" && r.kind === "module")).toBe(true);
|
||||
});
|
||||
|
||||
it("extracts variables and outputs", () => {
|
||||
const content = 'variable "region" {\n default = "us-east-1"\n}\n\noutput "bucket_arn" {\n value = aws_s3_bucket.main.arn\n}';
|
||||
const result = parser.analyzeFile("variables.tf", content);
|
||||
expect(result.definitions).toBeDefined();
|
||||
expect(result.definitions!.some(d => d.name === "region" && d.kind === "variable")).toBe(true);
|
||||
expect(result.definitions!.some(d => d.name === "bucket_arn" && d.kind === "output")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("MakefileParser", () => {
|
||||
const parser = new MakefileParser();
|
||||
|
||||
it("extracts make targets", () => {
|
||||
const content = "build:\n\tgo build -o bin/app\n\ntest:\n\tgo test ./...\n\nclean:\n\trm -rf bin/";
|
||||
const result = parser.analyzeFile("Makefile", content);
|
||||
expect(result.steps).toBeDefined();
|
||||
expect(result.steps!).toHaveLength(3);
|
||||
expect(result.steps!.map(s => s.name)).toEqual(["build", "test", "clean"]);
|
||||
});
|
||||
|
||||
it("does not confuse variable assignments with targets", () => {
|
||||
const content = "CC := gcc\nCFLAGS := -Wall\n\nbuild:\n\t$(CC) $(CFLAGS) main.c";
|
||||
const result = parser.analyzeFile("Makefile", content);
|
||||
expect(result.steps!).toHaveLength(1);
|
||||
expect(result.steps![0].name).toBe("build");
|
||||
});
|
||||
});
|
||||
|
||||
describe("ShellParser", () => {
|
||||
const parser = new ShellParser();
|
||||
|
||||
it("extracts function definitions", () => {
|
||||
const content = "#!/bin/bash\n\ngreet() {\n echo \"Hello $1\"\n}\n\nfunction cleanup {\n rm -rf tmp/\n}";
|
||||
const result = parser.analyzeFile("script.sh", content);
|
||||
expect(result.functions).toHaveLength(2);
|
||||
expect(result.functions[0].name).toBe("greet");
|
||||
expect(result.functions[1].name).toBe("cleanup");
|
||||
});
|
||||
|
||||
it("extracts source references", () => {
|
||||
const content = "#!/bin/bash\nsource ./lib/utils.sh\n. ./lib/config.sh";
|
||||
const refs = parser.extractReferences!("script.sh", content);
|
||||
expect(refs).toHaveLength(2);
|
||||
expect(refs[0]).toMatchObject({ target: "./lib/utils.sh", referenceType: "file" });
|
||||
expect(refs[1]).toMatchObject({ target: "./lib/config.sh", referenceType: "file" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("registerAllParsers", () => {
|
||||
it("registers all 12 parsers with a PluginRegistry", () => {
|
||||
const registry = new PluginRegistry();
|
||||
registerAllParsers(registry);
|
||||
expect(registry.getPlugins()).toHaveLength(12);
|
||||
expect(registry.getSupportedLanguages()).toContain("markdown");
|
||||
expect(registry.getSupportedLanguages()).toContain("yaml");
|
||||
expect(registry.getSupportedLanguages()).toContain("json");
|
||||
expect(registry.getSupportedLanguages()).toContain("toml");
|
||||
expect(registry.getSupportedLanguages()).toContain("env");
|
||||
expect(registry.getSupportedLanguages()).toContain("dockerfile");
|
||||
expect(registry.getSupportedLanguages()).toContain("sql");
|
||||
expect(registry.getSupportedLanguages()).toContain("graphql");
|
||||
expect(registry.getSupportedLanguages()).toContain("protobuf");
|
||||
expect(registry.getSupportedLanguages()).toContain("terraform");
|
||||
expect(registry.getSupportedLanguages()).toContain("makefile");
|
||||
expect(registry.getSupportedLanguages()).toContain("shell");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
import type { AnalyzerPlugin, StructuralAnalysis, ServiceInfo, StepInfo } from "../../types.js";
|
||||
|
||||
export class DockerfileParser implements AnalyzerPlugin {
|
||||
name = "dockerfile-parser";
|
||||
languages = ["dockerfile"];
|
||||
|
||||
analyzeFile(_filePath: string, content: string): StructuralAnalysis {
|
||||
const services = this.extractStages(content);
|
||||
const steps = this.extractSteps(content);
|
||||
return {
|
||||
functions: [],
|
||||
classes: [],
|
||||
imports: [],
|
||||
exports: [],
|
||||
services,
|
||||
steps,
|
||||
};
|
||||
}
|
||||
|
||||
private extractStages(content: string): ServiceInfo[] {
|
||||
const stages: ServiceInfo[] = [];
|
||||
const lines = content.split("\n");
|
||||
const ports: number[] = [];
|
||||
|
||||
// Collect all EXPOSE ports
|
||||
for (const line of lines) {
|
||||
const exposeMatch = line.match(/^EXPOSE\s+(.+)/i);
|
||||
if (exposeMatch) {
|
||||
const portValues = exposeMatch[1].split(/\s+/);
|
||||
for (const p of portValues) {
|
||||
const num = parseInt(p, 10);
|
||||
if (!isNaN(num)) ports.push(num);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract FROM stages
|
||||
for (const line of lines) {
|
||||
const fromMatch = line.match(/^FROM\s+(\S+)(?:\s+[Aa][Ss]\s+(\S+))?/i);
|
||||
if (fromMatch) {
|
||||
const image = fromMatch[1];
|
||||
const name = fromMatch[2] ?? image.split(":")[0].split("/").pop() ?? image;
|
||||
stages.push({
|
||||
name,
|
||||
image,
|
||||
ports: stages.length === 0 ? ports : [], // Assign ports to first stage only as default
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return stages;
|
||||
}
|
||||
|
||||
private extractSteps(content: string): StepInfo[] {
|
||||
const steps: StepInfo[] = [];
|
||||
const lines = content.split("\n");
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const match = lines[i].match(/^(FROM|RUN|COPY|ADD|WORKDIR|CMD|ENTRYPOINT|ENV|ARG|EXPOSE|VOLUME|USER|HEALTHCHECK)\s/i);
|
||||
if (match) {
|
||||
steps.push({
|
||||
name: `${match[1].toUpperCase()} ${lines[i].slice(match[1].length + 1).trim().slice(0, 60)}`,
|
||||
lineRange: [i + 1, i + 1],
|
||||
});
|
||||
}
|
||||
}
|
||||
return steps;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import type { AnalyzerPlugin, StructuralAnalysis, DefinitionInfo } from "../../types.js";
|
||||
|
||||
export class EnvParser implements AnalyzerPlugin {
|
||||
name = "env-parser";
|
||||
languages = ["env"];
|
||||
|
||||
analyzeFile(_filePath: string, content: string): StructuralAnalysis {
|
||||
const definitions = this.extractVariables(content);
|
||||
return {
|
||||
functions: [],
|
||||
classes: [],
|
||||
imports: [],
|
||||
exports: [],
|
||||
definitions,
|
||||
};
|
||||
}
|
||||
|
||||
private extractVariables(content: string): DefinitionInfo[] {
|
||||
const definitions: DefinitionInfo[] = [];
|
||||
const lines = content.split("\n");
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
if (line.startsWith("#") || line === "") continue;
|
||||
const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=/);
|
||||
if (match) {
|
||||
definitions.push({
|
||||
name: match[1],
|
||||
kind: "variable",
|
||||
lineRange: [i + 1, i + 1],
|
||||
fields: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
return definitions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import type { AnalyzerPlugin, StructuralAnalysis, DefinitionInfo, EndpointInfo } from "../../types.js";
|
||||
|
||||
export class GraphQLParser implements AnalyzerPlugin {
|
||||
name = "graphql-parser";
|
||||
languages = ["graphql"];
|
||||
|
||||
analyzeFile(_filePath: string, content: string): StructuralAnalysis {
|
||||
const definitions = this.extractDefinitions(content);
|
||||
const endpoints = this.extractEndpoints(content);
|
||||
return {
|
||||
functions: [],
|
||||
classes: [],
|
||||
imports: [],
|
||||
exports: [],
|
||||
definitions,
|
||||
endpoints,
|
||||
};
|
||||
}
|
||||
|
||||
private extractDefinitions(content: string): DefinitionInfo[] {
|
||||
const definitions: DefinitionInfo[] = [];
|
||||
const lines = content.split("\n");
|
||||
|
||||
// Match type, input, enum, interface, union, scalar definitions
|
||||
const typeRegex = /^(type|input|enum|interface|union|scalar)\s+(\w+)/gm;
|
||||
let match;
|
||||
while ((match = typeRegex.exec(content)) !== null) {
|
||||
const kind = match[1];
|
||||
const name = match[2];
|
||||
if (name === "Query" || name === "Mutation" || name === "Subscription") continue;
|
||||
const startLine = content.slice(0, match.index).split("\n").length;
|
||||
|
||||
// Extract fields (for type/input/interface/enum)
|
||||
const fields = this.extractFields(content, match.index);
|
||||
|
||||
// Find closing brace
|
||||
const afterMatch = content.slice(match.index);
|
||||
const closeBrace = afterMatch.indexOf("}");
|
||||
const endLine = closeBrace !== -1
|
||||
? content.slice(0, match.index + closeBrace + 1).split("\n").length
|
||||
: startLine;
|
||||
|
||||
definitions.push({
|
||||
name,
|
||||
kind,
|
||||
lineRange: [startLine, endLine],
|
||||
fields,
|
||||
});
|
||||
}
|
||||
|
||||
return definitions;
|
||||
}
|
||||
|
||||
private extractEndpoints(content: string): EndpointInfo[] {
|
||||
const endpoints: EndpointInfo[] = [];
|
||||
|
||||
// Find Query, Mutation, Subscription blocks and extract their fields
|
||||
const blockRegex = /^(type)\s+(Query|Mutation|Subscription)\s*\{/gm;
|
||||
let match;
|
||||
while ((match = blockRegex.exec(content)) !== null) {
|
||||
const method = match[2]; // Query, Mutation, Subscription
|
||||
const startIdx = match.index + match[0].length;
|
||||
|
||||
// Find closing brace
|
||||
let depth = 1;
|
||||
let i = startIdx;
|
||||
while (i < content.length && depth > 0) {
|
||||
if (content[i] === "{") depth++;
|
||||
if (content[i] === "}") depth--;
|
||||
i++;
|
||||
}
|
||||
|
||||
const blockContent = content.slice(startIdx, i - 1);
|
||||
const blockLines = blockContent.split("\n");
|
||||
const blockStartLine = content.slice(0, startIdx).split("\n").length;
|
||||
|
||||
for (let j = 0; j < blockLines.length; j++) {
|
||||
const fieldMatch = blockLines[j].trim().match(/^(\w+)/);
|
||||
if (fieldMatch && fieldMatch[1]) {
|
||||
const lineNum = blockStartLine + j;
|
||||
endpoints.push({
|
||||
method,
|
||||
path: fieldMatch[1],
|
||||
lineRange: [lineNum, lineNum],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
private extractFields(content: string, startIdx: number): string[] {
|
||||
const fields: string[] = [];
|
||||
const afterType = content.slice(startIdx);
|
||||
const openBrace = afterType.indexOf("{");
|
||||
if (openBrace === -1) return fields;
|
||||
|
||||
let depth = 1;
|
||||
let i = openBrace + 1;
|
||||
while (i < afterType.length && depth > 0) {
|
||||
if (afterType[i] === "{") depth++;
|
||||
if (afterType[i] === "}") depth--;
|
||||
i++;
|
||||
}
|
||||
|
||||
const body = afterType.slice(openBrace + 1, i - 1);
|
||||
const lines = body.split("\n");
|
||||
for (const line of lines) {
|
||||
const fieldMatch = line.trim().match(/^(\w+)/);
|
||||
if (fieldMatch) {
|
||||
fields.push(fieldMatch[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
export { MarkdownParser } from "./markdown-parser.js";
|
||||
export { YAMLConfigParser } from "./yaml-parser.js";
|
||||
export { JSONConfigParser } from "./json-parser.js";
|
||||
export { TOMLParser } from "./toml-parser.js";
|
||||
export { EnvParser } from "./env-parser.js";
|
||||
export { DockerfileParser } from "./dockerfile-parser.js";
|
||||
export { SQLParser } from "./sql-parser.js";
|
||||
export { GraphQLParser } from "./graphql-parser.js";
|
||||
export { ProtobufParser } from "./protobuf-parser.js";
|
||||
export { TerraformParser } from "./terraform-parser.js";
|
||||
export { MakefileParser } from "./makefile-parser.js";
|
||||
export { ShellParser } from "./shell-parser.js";
|
||||
|
||||
import type { PluginRegistry } from "../registry.js";
|
||||
import { MarkdownParser } from "./markdown-parser.js";
|
||||
import { YAMLConfigParser } from "./yaml-parser.js";
|
||||
import { JSONConfigParser } from "./json-parser.js";
|
||||
import { TOMLParser } from "./toml-parser.js";
|
||||
import { EnvParser } from "./env-parser.js";
|
||||
import { DockerfileParser } from "./dockerfile-parser.js";
|
||||
import { SQLParser } from "./sql-parser.js";
|
||||
import { GraphQLParser } from "./graphql-parser.js";
|
||||
import { ProtobufParser } from "./protobuf-parser.js";
|
||||
import { TerraformParser } from "./terraform-parser.js";
|
||||
import { MakefileParser } from "./makefile-parser.js";
|
||||
import { ShellParser } from "./shell-parser.js";
|
||||
|
||||
/**
|
||||
* Register all built-in non-code parsers with a PluginRegistry.
|
||||
*/
|
||||
export function registerAllParsers(registry: PluginRegistry): void {
|
||||
registry.register(new MarkdownParser());
|
||||
registry.register(new YAMLConfigParser());
|
||||
registry.register(new JSONConfigParser());
|
||||
registry.register(new TOMLParser());
|
||||
registry.register(new EnvParser());
|
||||
registry.register(new DockerfileParser());
|
||||
registry.register(new SQLParser());
|
||||
registry.register(new GraphQLParser());
|
||||
registry.register(new ProtobufParser());
|
||||
registry.register(new TerraformParser());
|
||||
registry.register(new MakefileParser());
|
||||
registry.register(new ShellParser());
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import type { AnalyzerPlugin, StructuralAnalysis, SectionInfo, ReferenceResolution } from "../../types.js";
|
||||
|
||||
export class JSONConfigParser implements AnalyzerPlugin {
|
||||
name = "json-config-parser";
|
||||
languages = ["json"];
|
||||
|
||||
analyzeFile(_filePath: string, content: string): StructuralAnalysis {
|
||||
const sections = this.extractSections(content);
|
||||
return {
|
||||
functions: [],
|
||||
classes: [],
|
||||
imports: [],
|
||||
exports: [],
|
||||
sections,
|
||||
};
|
||||
}
|
||||
|
||||
extractReferences(filePath: string, content: string): ReferenceResolution[] {
|
||||
const refs: ReferenceResolution[] = [];
|
||||
// Match $ref values (JSON Schema / OpenAPI)
|
||||
const refRegex = /"\$ref"\s*:\s*"([^"]+)"/g;
|
||||
let match;
|
||||
while ((match = refRegex.exec(content)) !== null) {
|
||||
const target = match[1];
|
||||
if (target.startsWith("#")) continue; // Skip internal refs
|
||||
const line = content.slice(0, match.index).split("\n").length;
|
||||
refs.push({
|
||||
source: filePath,
|
||||
target,
|
||||
referenceType: "schema",
|
||||
line,
|
||||
});
|
||||
}
|
||||
return refs;
|
||||
}
|
||||
|
||||
private extractSections(content: string): SectionInfo[] {
|
||||
const sections: SectionInfo[] = [];
|
||||
try {
|
||||
const doc = JSON.parse(content);
|
||||
if (doc && typeof doc === "object" && !Array.isArray(doc)) {
|
||||
const lines = content.split("\n");
|
||||
for (const key of Object.keys(doc)) {
|
||||
const escapedKey = JSON.stringify(key);
|
||||
const lineIdx = lines.findIndex((l) => l.includes(escapedKey));
|
||||
if (lineIdx !== -1) {
|
||||
sections.push({
|
||||
name: key,
|
||||
level: 1,
|
||||
lineRange: [lineIdx + 1, lineIdx + 1],
|
||||
});
|
||||
}
|
||||
}
|
||||
// Fix lineRange end
|
||||
for (let i = 0; i < sections.length; i++) {
|
||||
const next = sections[i + 1];
|
||||
sections[i].lineRange[1] = next ? next.lineRange[0] - 1 : lines.length;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// JSON parse failed — skip
|
||||
}
|
||||
return sections;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import type { AnalyzerPlugin, StructuralAnalysis, StepInfo } from "../../types.js";
|
||||
|
||||
export class MakefileParser implements AnalyzerPlugin {
|
||||
name = "makefile-parser";
|
||||
languages = ["makefile"];
|
||||
|
||||
analyzeFile(_filePath: string, content: string): StructuralAnalysis {
|
||||
const steps = this.extractTargets(content);
|
||||
return {
|
||||
functions: [],
|
||||
classes: [],
|
||||
imports: [],
|
||||
exports: [],
|
||||
steps,
|
||||
};
|
||||
}
|
||||
|
||||
private extractTargets(content: string): StepInfo[] {
|
||||
const targets: StepInfo[] = [];
|
||||
const lines = content.split("\n");
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
// Match target: dependencies (not variable assignments or comments)
|
||||
const match = lines[i].match(/^([a-zA-Z_][\w.-]*)(?:\s+.*)?:/);
|
||||
if (match && !lines[i].includes(":=") && !lines[i].includes("?=")) {
|
||||
// Find end of target (next non-indented non-empty line or EOF)
|
||||
let endLine = i + 1;
|
||||
while (endLine < lines.length) {
|
||||
const nextLine = lines[endLine];
|
||||
if (nextLine === "" || nextLine.startsWith("\t") || nextLine.startsWith(" ")) {
|
||||
endLine++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
targets.push({
|
||||
name: match[1],
|
||||
lineRange: [i + 1, endLine],
|
||||
});
|
||||
}
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import type { AnalyzerPlugin, StructuralAnalysis, ReferenceResolution, SectionInfo } from "../../types.js";
|
||||
|
||||
export class MarkdownParser implements AnalyzerPlugin {
|
||||
name = "markdown-parser";
|
||||
languages = ["markdown"];
|
||||
|
||||
analyzeFile(_filePath: string, content: string): StructuralAnalysis {
|
||||
const sections = this.extractSections(content);
|
||||
return {
|
||||
functions: [],
|
||||
classes: [],
|
||||
imports: [],
|
||||
exports: [],
|
||||
sections,
|
||||
};
|
||||
}
|
||||
|
||||
extractReferences(filePath: string, content: string): ReferenceResolution[] {
|
||||
const refs: ReferenceResolution[] = [];
|
||||
const linkRegex = /!?\[([^\]]*)\]\(([^)]+)\)/g;
|
||||
let match;
|
||||
while ((match = linkRegex.exec(content)) !== null) {
|
||||
const target = match[2];
|
||||
if (target.startsWith("http")) continue; // Skip external URLs
|
||||
const line = content.slice(0, match.index).split("\n").length;
|
||||
refs.push({
|
||||
source: filePath,
|
||||
target,
|
||||
referenceType: match[0].startsWith("!") ? "image" : "file",
|
||||
line,
|
||||
});
|
||||
}
|
||||
return refs;
|
||||
}
|
||||
|
||||
private extractSections(content: string): SectionInfo[] {
|
||||
const sections: SectionInfo[] = [];
|
||||
const lines = content.split("\n");
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const match = lines[i].match(/^(#{1,6})\s+(.+)/);
|
||||
if (match) {
|
||||
sections.push({
|
||||
name: match[2].trim(),
|
||||
level: match[1].length,
|
||||
lineRange: [i + 1, i + 1],
|
||||
});
|
||||
}
|
||||
}
|
||||
// Fix lineRange end for each section (extends to next heading or EOF)
|
||||
for (let i = 0; i < sections.length; i++) {
|
||||
const next = sections[i + 1];
|
||||
sections[i].lineRange[1] = next ? next.lineRange[0] - 1 : lines.length;
|
||||
}
|
||||
return sections;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import type { AnalyzerPlugin, StructuralAnalysis, DefinitionInfo, EndpointInfo } from "../../types.js";
|
||||
|
||||
export class ProtobufParser implements AnalyzerPlugin {
|
||||
name = "protobuf-parser";
|
||||
languages = ["protobuf"];
|
||||
|
||||
analyzeFile(_filePath: string, content: string): StructuralAnalysis {
|
||||
const definitions = this.extractDefinitions(content);
|
||||
const endpoints = this.extractServiceMethods(content);
|
||||
return {
|
||||
functions: [],
|
||||
classes: [],
|
||||
imports: [],
|
||||
exports: [],
|
||||
definitions,
|
||||
endpoints,
|
||||
};
|
||||
}
|
||||
|
||||
private extractDefinitions(content: string): DefinitionInfo[] {
|
||||
const definitions: DefinitionInfo[] = [];
|
||||
|
||||
// Match message definitions
|
||||
const messageRegex = /^message\s+(\w+)\s*\{/gm;
|
||||
let match;
|
||||
while ((match = messageRegex.exec(content)) !== null) {
|
||||
const startLine = content.slice(0, match.index).split("\n").length;
|
||||
const fields = this.extractMessageFields(content, match.index);
|
||||
const afterMatch = content.slice(match.index);
|
||||
const closeBrace = this.findClosingBrace(afterMatch);
|
||||
const endLine = content.slice(0, match.index + closeBrace + 1).split("\n").length;
|
||||
|
||||
definitions.push({
|
||||
name: match[1],
|
||||
kind: "message",
|
||||
lineRange: [startLine, endLine],
|
||||
fields,
|
||||
});
|
||||
}
|
||||
|
||||
// Match enum definitions
|
||||
const enumRegex = /^enum\s+(\w+)\s*\{/gm;
|
||||
while ((match = enumRegex.exec(content)) !== null) {
|
||||
const startLine = content.slice(0, match.index).split("\n").length;
|
||||
const fields = this.extractEnumValues(content, match.index);
|
||||
const afterMatch = content.slice(match.index);
|
||||
const closeBrace = this.findClosingBrace(afterMatch);
|
||||
const endLine = content.slice(0, match.index + closeBrace + 1).split("\n").length;
|
||||
|
||||
definitions.push({
|
||||
name: match[1],
|
||||
kind: "enum",
|
||||
lineRange: [startLine, endLine],
|
||||
fields,
|
||||
});
|
||||
}
|
||||
|
||||
return definitions;
|
||||
}
|
||||
|
||||
private extractServiceMethods(content: string): EndpointInfo[] {
|
||||
const endpoints: EndpointInfo[] = [];
|
||||
const serviceRegex = /^service\s+(\w+)\s*\{/gm;
|
||||
let match;
|
||||
while ((match = serviceRegex.exec(content)) !== null) {
|
||||
const serviceName = match[1];
|
||||
const startIdx = match.index + match[0].length;
|
||||
const afterService = content.slice(match.index);
|
||||
const closeBrace = this.findClosingBrace(afterService);
|
||||
const body = afterService.slice(match[0].length, closeBrace);
|
||||
|
||||
const rpcRegex = /rpc\s+(\w+)\s*\(/g;
|
||||
let rpcMatch;
|
||||
while ((rpcMatch = rpcRegex.exec(body)) !== null) {
|
||||
const lineNum = content.slice(0, startIdx + rpcMatch.index).split("\n").length;
|
||||
endpoints.push({
|
||||
method: "rpc",
|
||||
path: `${serviceName}.${rpcMatch[1]}`,
|
||||
lineRange: [lineNum, lineNum],
|
||||
});
|
||||
}
|
||||
}
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
private extractMessageFields(content: string, startIdx: number): string[] {
|
||||
const fields: string[] = [];
|
||||
const afterMsg = content.slice(startIdx);
|
||||
const openBrace = afterMsg.indexOf("{");
|
||||
if (openBrace === -1) return fields;
|
||||
|
||||
const closeBrace = this.findClosingBrace(afterMsg);
|
||||
const body = afterMsg.slice(openBrace + 1, closeBrace);
|
||||
|
||||
const fieldRegex = /^\s*(?:repeated\s+|optional\s+|required\s+|map<[^>]+>\s+)?\w+\s+(\w+)\s*=/gm;
|
||||
let match;
|
||||
while ((match = fieldRegex.exec(body)) !== null) {
|
||||
fields.push(match[1]);
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
private extractEnumValues(content: string, startIdx: number): string[] {
|
||||
const values: string[] = [];
|
||||
const afterEnum = content.slice(startIdx);
|
||||
const openBrace = afterEnum.indexOf("{");
|
||||
if (openBrace === -1) return values;
|
||||
|
||||
const closeBrace = this.findClosingBrace(afterEnum);
|
||||
const body = afterEnum.slice(openBrace + 1, closeBrace);
|
||||
|
||||
const valueRegex = /^\s*(\w+)\s*=/gm;
|
||||
let match;
|
||||
while ((match = valueRegex.exec(body)) !== null) {
|
||||
values.push(match[1]);
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
private findClosingBrace(content: string): number {
|
||||
let depth = 0;
|
||||
for (let i = 0; i < content.length; i++) {
|
||||
if (content[i] === "{") depth++;
|
||||
if (content[i] === "}") {
|
||||
depth--;
|
||||
if (depth === 0) return i;
|
||||
}
|
||||
}
|
||||
return content.length;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import type { AnalyzerPlugin, StructuralAnalysis, ReferenceResolution } from "../../types.js";
|
||||
|
||||
export class ShellParser implements AnalyzerPlugin {
|
||||
name = "shell-parser";
|
||||
languages = ["shell"];
|
||||
|
||||
analyzeFile(_filePath: string, content: string): StructuralAnalysis {
|
||||
const functions = this.extractFunctions(content);
|
||||
return {
|
||||
functions,
|
||||
classes: [],
|
||||
imports: [],
|
||||
exports: [],
|
||||
};
|
||||
}
|
||||
|
||||
extractReferences(filePath: string, content: string): ReferenceResolution[] {
|
||||
const refs: ReferenceResolution[] = [];
|
||||
const lines = content.split("\n");
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
// Match source/. commands
|
||||
const sourceMatch = lines[i].match(/^\s*(?:source|\.)[ \t]+["']?([^"'\s]+)["']?/);
|
||||
if (sourceMatch) {
|
||||
refs.push({
|
||||
source: filePath,
|
||||
target: sourceMatch[1],
|
||||
referenceType: "file",
|
||||
line: i + 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
return refs;
|
||||
}
|
||||
|
||||
private extractFunctions(content: string): Array<{ name: string; lineRange: [number, number]; params: string[] }> {
|
||||
const functions: Array<{ name: string; lineRange: [number, number]; params: string[] }> = [];
|
||||
const lines = content.split("\n");
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
// Match function name() { or function name {
|
||||
const match = lines[i].match(/^(?:function\s+)?(\w+)\s*\(\s*\)\s*\{?/) ||
|
||||
lines[i].match(/^function\s+(\w+)\s*\{?/);
|
||||
if (match) {
|
||||
const name = match[1];
|
||||
// Find closing brace
|
||||
let endLine = i;
|
||||
if (lines[i].includes("{")) {
|
||||
let depth = 0;
|
||||
for (let j = i; j < lines.length; j++) {
|
||||
for (const ch of lines[j]) {
|
||||
if (ch === "{") depth++;
|
||||
if (ch === "}") depth--;
|
||||
}
|
||||
if (depth === 0) {
|
||||
endLine = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
functions.push({
|
||||
name,
|
||||
lineRange: [i + 1, endLine + 1],
|
||||
params: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return functions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import type { AnalyzerPlugin, StructuralAnalysis, DefinitionInfo } from "../../types.js";
|
||||
|
||||
export class SQLParser implements AnalyzerPlugin {
|
||||
name = "sql-parser";
|
||||
languages = ["sql"];
|
||||
|
||||
analyzeFile(_filePath: string, content: string): StructuralAnalysis {
|
||||
const definitions = this.extractDefinitions(content);
|
||||
return {
|
||||
functions: [],
|
||||
classes: [],
|
||||
imports: [],
|
||||
exports: [],
|
||||
definitions,
|
||||
};
|
||||
}
|
||||
|
||||
private extractDefinitions(content: string): DefinitionInfo[] {
|
||||
const definitions: DefinitionInfo[] = [];
|
||||
const lines = content.split("\n");
|
||||
|
||||
// Match CREATE TABLE statements
|
||||
const tableRegex = /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:`|")?(\w+)(?:`|")?/gi;
|
||||
let match;
|
||||
while ((match = tableRegex.exec(content)) !== null) {
|
||||
const tableName = match[1];
|
||||
const startLine = content.slice(0, match.index).split("\n").length;
|
||||
|
||||
// Extract columns (simplified: look for column names in parenthesized block)
|
||||
const fields = this.extractColumns(content, match.index);
|
||||
|
||||
// Find the end of the CREATE TABLE statement
|
||||
const afterMatch = content.slice(match.index);
|
||||
const endParen = afterMatch.indexOf(");");
|
||||
const endLine = endParen !== -1
|
||||
? content.slice(0, match.index + endParen + 2).split("\n").length
|
||||
: startLine + 5;
|
||||
|
||||
definitions.push({
|
||||
name: tableName,
|
||||
kind: "table",
|
||||
lineRange: [startLine, endLine],
|
||||
fields,
|
||||
});
|
||||
}
|
||||
|
||||
// Match CREATE VIEW
|
||||
const viewRegex = /CREATE\s+(?:OR\s+REPLACE\s+)?VIEW\s+(?:`|")?(\w+)(?:`|")?/gi;
|
||||
while ((match = viewRegex.exec(content)) !== null) {
|
||||
const startLine = content.slice(0, match.index).split("\n").length;
|
||||
definitions.push({
|
||||
name: match[1],
|
||||
kind: "view",
|
||||
lineRange: [startLine, startLine],
|
||||
fields: [],
|
||||
});
|
||||
}
|
||||
|
||||
// Match CREATE INDEX
|
||||
const indexRegex = /CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:`|")?(\w+)(?:`|")?/gi;
|
||||
while ((match = indexRegex.exec(content)) !== null) {
|
||||
const startLine = content.slice(0, match.index).split("\n").length;
|
||||
definitions.push({
|
||||
name: match[1],
|
||||
kind: "index",
|
||||
lineRange: [startLine, startLine],
|
||||
fields: [],
|
||||
});
|
||||
}
|
||||
|
||||
return definitions;
|
||||
}
|
||||
|
||||
private extractColumns(content: string, startIdx: number): string[] {
|
||||
const fields: string[] = [];
|
||||
const afterCreate = content.slice(startIdx);
|
||||
const openParen = afterCreate.indexOf("(");
|
||||
if (openParen === -1) return fields;
|
||||
|
||||
const closeParen = afterCreate.indexOf(");", openParen);
|
||||
if (closeParen === -1) return fields;
|
||||
|
||||
const body = afterCreate.slice(openParen + 1, closeParen);
|
||||
const lines = body.split(",");
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
// Skip constraints
|
||||
if (/^(PRIMARY|FOREIGN|UNIQUE|CHECK|CONSTRAINT|INDEX|KEY)/i.test(trimmed)) continue;
|
||||
const colMatch = trimmed.match(/^(?:`|")?(\w+)(?:`|")?\s+/);
|
||||
if (colMatch) {
|
||||
fields.push(colMatch[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import type { AnalyzerPlugin, StructuralAnalysis, ResourceInfo, DefinitionInfo } from "../../types.js";
|
||||
|
||||
export class TerraformParser implements AnalyzerPlugin {
|
||||
name = "terraform-parser";
|
||||
languages = ["terraform"];
|
||||
|
||||
analyzeFile(_filePath: string, content: string): StructuralAnalysis {
|
||||
const resources = this.extractResources(content);
|
||||
const definitions = this.extractVariablesAndOutputs(content);
|
||||
return {
|
||||
functions: [],
|
||||
classes: [],
|
||||
imports: [],
|
||||
exports: [],
|
||||
resources,
|
||||
definitions,
|
||||
};
|
||||
}
|
||||
|
||||
private extractResources(content: string): ResourceInfo[] {
|
||||
const resources: ResourceInfo[] = [];
|
||||
|
||||
// Match resource blocks: resource "type" "name" {
|
||||
const resourceRegex = /^resource\s+"([^"]+)"\s+"([^"]+)"\s*\{/gm;
|
||||
let match;
|
||||
while ((match = resourceRegex.exec(content)) !== null) {
|
||||
const startLine = content.slice(0, match.index).split("\n").length;
|
||||
const afterMatch = content.slice(match.index);
|
||||
const closeBrace = this.findClosingBrace(afterMatch);
|
||||
const endLine = content.slice(0, match.index + closeBrace + 1).split("\n").length;
|
||||
|
||||
resources.push({
|
||||
name: `${match[1]}.${match[2]}`,
|
||||
kind: match[1],
|
||||
lineRange: [startLine, endLine],
|
||||
});
|
||||
}
|
||||
|
||||
// Match data blocks: data "type" "name" {
|
||||
const dataRegex = /^data\s+"([^"]+)"\s+"([^"]+)"\s*\{/gm;
|
||||
while ((match = dataRegex.exec(content)) !== null) {
|
||||
const startLine = content.slice(0, match.index).split("\n").length;
|
||||
const afterMatch = content.slice(match.index);
|
||||
const closeBrace = this.findClosingBrace(afterMatch);
|
||||
const endLine = content.slice(0, match.index + closeBrace + 1).split("\n").length;
|
||||
|
||||
resources.push({
|
||||
name: `data.${match[1]}.${match[2]}`,
|
||||
kind: `data.${match[1]}`,
|
||||
lineRange: [startLine, endLine],
|
||||
});
|
||||
}
|
||||
|
||||
// Match module blocks: module "name" {
|
||||
const moduleRegex = /^module\s+"([^"]+)"\s*\{/gm;
|
||||
while ((match = moduleRegex.exec(content)) !== null) {
|
||||
const startLine = content.slice(0, match.index).split("\n").length;
|
||||
const afterMatch = content.slice(match.index);
|
||||
const closeBrace = this.findClosingBrace(afterMatch);
|
||||
const endLine = content.slice(0, match.index + closeBrace + 1).split("\n").length;
|
||||
|
||||
resources.push({
|
||||
name: `module.${match[1]}`,
|
||||
kind: "module",
|
||||
lineRange: [startLine, endLine],
|
||||
});
|
||||
}
|
||||
|
||||
return resources;
|
||||
}
|
||||
|
||||
private extractVariablesAndOutputs(content: string): DefinitionInfo[] {
|
||||
const definitions: DefinitionInfo[] = [];
|
||||
|
||||
// Match variable blocks
|
||||
const varRegex = /^variable\s+"([^"]+)"\s*\{/gm;
|
||||
let match;
|
||||
while ((match = varRegex.exec(content)) !== null) {
|
||||
const startLine = content.slice(0, match.index).split("\n").length;
|
||||
const afterMatch = content.slice(match.index);
|
||||
const closeBrace = this.findClosingBrace(afterMatch);
|
||||
const endLine = content.slice(0, match.index + closeBrace + 1).split("\n").length;
|
||||
|
||||
definitions.push({
|
||||
name: match[1],
|
||||
kind: "variable",
|
||||
lineRange: [startLine, endLine],
|
||||
fields: [],
|
||||
});
|
||||
}
|
||||
|
||||
// Match output blocks
|
||||
const outputRegex = /^output\s+"([^"]+)"\s*\{/gm;
|
||||
while ((match = outputRegex.exec(content)) !== null) {
|
||||
const startLine = content.slice(0, match.index).split("\n").length;
|
||||
const afterMatch = content.slice(match.index);
|
||||
const closeBrace = this.findClosingBrace(afterMatch);
|
||||
const endLine = content.slice(0, match.index + closeBrace + 1).split("\n").length;
|
||||
|
||||
definitions.push({
|
||||
name: match[1],
|
||||
kind: "output",
|
||||
lineRange: [startLine, endLine],
|
||||
fields: [],
|
||||
});
|
||||
}
|
||||
|
||||
return definitions;
|
||||
}
|
||||
|
||||
private findClosingBrace(content: string): number {
|
||||
let depth = 0;
|
||||
for (let i = 0; i < content.length; i++) {
|
||||
if (content[i] === "{") depth++;
|
||||
if (content[i] === "}") {
|
||||
depth--;
|
||||
if (depth === 0) return i;
|
||||
}
|
||||
}
|
||||
return content.length;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import type { AnalyzerPlugin, StructuralAnalysis, SectionInfo } from "../../types.js";
|
||||
|
||||
export class TOMLParser implements AnalyzerPlugin {
|
||||
name = "toml-parser";
|
||||
languages = ["toml"];
|
||||
|
||||
analyzeFile(_filePath: string, content: string): StructuralAnalysis {
|
||||
const sections = this.extractSections(content);
|
||||
return {
|
||||
functions: [],
|
||||
classes: [],
|
||||
imports: [],
|
||||
exports: [],
|
||||
sections,
|
||||
};
|
||||
}
|
||||
|
||||
private extractSections(content: string): SectionInfo[] {
|
||||
const sections: SectionInfo[] = [];
|
||||
const lines = content.split("\n");
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
// Match [section] and [[array-of-tables]] headers
|
||||
const match = lines[i].match(/^\s*\[(\[?)([^\]]+)\]?\]/);
|
||||
if (match) {
|
||||
const isArray = match[1] === "[";
|
||||
const name = match[2].trim();
|
||||
sections.push({
|
||||
name: isArray ? `[[${name}]]` : name,
|
||||
level: name.split(".").length,
|
||||
lineRange: [i + 1, i + 1],
|
||||
});
|
||||
}
|
||||
}
|
||||
// Fix lineRange end
|
||||
for (let i = 0; i < sections.length; i++) {
|
||||
const next = sections[i + 1];
|
||||
sections[i].lineRange[1] = next ? next.lineRange[0] - 1 : lines.length;
|
||||
}
|
||||
return sections;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import type { AnalyzerPlugin, StructuralAnalysis, SectionInfo } from "../../types.js";
|
||||
import { parse as parseYAML } from "yaml";
|
||||
|
||||
export class YAMLConfigParser implements AnalyzerPlugin {
|
||||
name = "yaml-config-parser";
|
||||
languages = ["yaml"];
|
||||
|
||||
analyzeFile(_filePath: string, content: string): StructuralAnalysis {
|
||||
const sections = this.extractSections(content);
|
||||
return {
|
||||
functions: [],
|
||||
classes: [],
|
||||
imports: [],
|
||||
exports: [],
|
||||
sections,
|
||||
};
|
||||
}
|
||||
|
||||
private extractSections(content: string): SectionInfo[] {
|
||||
const sections: SectionInfo[] = [];
|
||||
try {
|
||||
const doc = parseYAML(content);
|
||||
if (doc && typeof doc === "object" && !Array.isArray(doc)) {
|
||||
const lines = content.split("\n");
|
||||
for (const key of Object.keys(doc)) {
|
||||
// Find the line where this top-level key appears
|
||||
const lineIdx = lines.findIndex((l) => l.match(new RegExp(`^${this.escapeRegex(key)}\\s*:`)));
|
||||
if (lineIdx !== -1) {
|
||||
sections.push({
|
||||
name: key,
|
||||
level: 1,
|
||||
lineRange: [lineIdx + 1, lineIdx + 1],
|
||||
});
|
||||
}
|
||||
}
|
||||
// Fix lineRange end
|
||||
for (let i = 0; i < sections.length; i++) {
|
||||
const next = sections[i + 1];
|
||||
sections[i].lineRange[1] = next ? next.lineRange[0] - 1 : lines.length;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// If YAML parsing fails, fall back to regex
|
||||
const lines = content.split("\n");
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const match = lines[i].match(/^(\w[\w-]*)\s*:/);
|
||||
if (match) {
|
||||
sections.push({
|
||||
name: match[1],
|
||||
level: 1,
|
||||
lineRange: [i + 1, i + 1],
|
||||
});
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < sections.length; i++) {
|
||||
const next = sections[i + 1];
|
||||
sections[i].lineRange[1] = next ? next.lineRange[0] - 1 : lines.length;
|
||||
}
|
||||
}
|
||||
return sections;
|
||||
}
|
||||
|
||||
private escapeRegex(str: string): string {
|
||||
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
}
|
||||
+41
-57
@@ -20,19 +20,23 @@ importers:
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: ^3.1.0
|
||||
version: 3.2.4(@types/debug@4.1.13)(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)
|
||||
version: 3.2.4(@types/debug@4.1.13)(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)
|
||||
|
||||
packages/core:
|
||||
dependencies:
|
||||
yaml:
|
||||
specifier: ^2.8.3
|
||||
version: 2.8.3
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^25.5.0
|
||||
version: 25.5.0
|
||||
'@vitest/coverage-v8':
|
||||
specifier: 3.2.4
|
||||
version: 3.2.4(vitest@3.2.4(@types/debug@4.1.13)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0))
|
||||
version: 3.2.4(vitest@3.2.4(@types/debug@4.1.13)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3))
|
||||
vitest:
|
||||
specifier: ^3.1.0
|
||||
version: 3.2.4(@types/debug@4.1.13)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)
|
||||
version: 3.2.4(@types/debug@4.1.13)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)
|
||||
|
||||
packages/dashboard:
|
||||
dependencies:
|
||||
@@ -66,7 +70,7 @@ importers:
|
||||
devDependencies:
|
||||
'@tailwindcss/vite':
|
||||
specifier: ^4.0.0
|
||||
version: 4.2.2(vite@6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0))
|
||||
version: 4.2.2(vite@6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3))
|
||||
'@types/react':
|
||||
specifier: ^19.0.0
|
||||
version: 19.2.14
|
||||
@@ -75,7 +79,7 @@ importers:
|
||||
version: 19.2.3(@types/react@19.2.14)
|
||||
'@vitejs/plugin-react':
|
||||
specifier: ^4.3.0
|
||||
version: 4.7.0(vite@6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0))
|
||||
version: 4.7.0(vite@6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3))
|
||||
tailwindcss:
|
||||
specifier: ^4.0.0
|
||||
version: 4.2.2
|
||||
@@ -84,7 +88,7 @@ importers:
|
||||
version: 5.9.3
|
||||
vite:
|
||||
specifier: ^6.0.0
|
||||
version: 6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)
|
||||
version: 6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)
|
||||
|
||||
packages:
|
||||
|
||||
@@ -406,79 +410,66 @@ packages:
|
||||
resolution: {integrity: sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.60.0':
|
||||
resolution: {integrity: sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.60.0':
|
||||
resolution: {integrity: sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.60.0':
|
||||
resolution: {integrity: sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-loong64-gnu@4.60.0':
|
||||
resolution: {integrity: sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-loong64-musl@4.60.0':
|
||||
resolution: {integrity: sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.60.0':
|
||||
resolution: {integrity: sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-musl@4.60.0':
|
||||
resolution: {integrity: sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.60.0':
|
||||
resolution: {integrity: sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.60.0':
|
||||
resolution: {integrity: sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.60.0':
|
||||
resolution: {integrity: sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.60.0':
|
||||
resolution: {integrity: sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.60.0':
|
||||
resolution: {integrity: sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-openbsd-x64@4.60.0':
|
||||
resolution: {integrity: sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==}
|
||||
@@ -548,28 +539,24 @@ packages:
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-musl@4.2.2':
|
||||
resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-gnu@4.2.2':
|
||||
resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-musl@4.2.2':
|
||||
resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==}
|
||||
engines: {node: '>= 20'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-wasm32-wasi@4.2.2':
|
||||
resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==}
|
||||
@@ -1092,28 +1079,24 @@ packages:
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.32.0:
|
||||
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.32.0:
|
||||
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-x64-musl@1.32.0:
|
||||
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.32.0:
|
||||
resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
|
||||
@@ -1579,6 +1562,11 @@ packages:
|
||||
yallist@3.1.1:
|
||||
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
||||
|
||||
yaml@2.8.3:
|
||||
resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==}
|
||||
engines: {node: '>= 14.6'}
|
||||
hasBin: true
|
||||
|
||||
zustand@4.5.7:
|
||||
resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==}
|
||||
engines: {node: '>=12.7.0'}
|
||||
@@ -1991,12 +1979,12 @@ snapshots:
|
||||
'@tailwindcss/oxide-win32-arm64-msvc': 4.2.2
|
||||
'@tailwindcss/oxide-win32-x64-msvc': 4.2.2
|
||||
|
||||
'@tailwindcss/vite@4.2.2(vite@6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0))':
|
||||
'@tailwindcss/vite@4.2.2(vite@6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3))':
|
||||
dependencies:
|
||||
'@tailwindcss/node': 4.2.2
|
||||
'@tailwindcss/oxide': 4.2.2
|
||||
tailwindcss: 4.2.2
|
||||
vite: 6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)
|
||||
vite: 6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
dependencies:
|
||||
@@ -2089,7 +2077,7 @@ snapshots:
|
||||
|
||||
'@ungap/structured-clone@1.3.0': {}
|
||||
|
||||
'@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0))':
|
||||
'@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3))':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0)
|
||||
@@ -2097,11 +2085,11 @@ snapshots:
|
||||
'@rolldown/pluginutils': 1.0.0-beta.27
|
||||
'@types/babel__core': 7.20.5
|
||||
react-refresh: 0.17.0
|
||||
vite: 6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)
|
||||
vite: 6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.13)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0))':
|
||||
'@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.13)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3))':
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@bcoe/v8-coverage': 1.0.2
|
||||
@@ -2116,7 +2104,7 @@ snapshots:
|
||||
std-env: 3.10.0
|
||||
test-exclude: 7.0.2
|
||||
tinyrainbow: 2.0.0
|
||||
vitest: 3.2.4(@types/debug@4.1.13)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)
|
||||
vitest: 3.2.4(@types/debug@4.1.13)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -2128,21 +2116,13 @@ snapshots:
|
||||
chai: 5.3.3
|
||||
tinyrainbow: 2.0.0
|
||||
|
||||
'@vitest/mocker@3.2.4(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0))':
|
||||
'@vitest/mocker@3.2.4(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3))':
|
||||
dependencies:
|
||||
'@vitest/spy': 3.2.4
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.21
|
||||
optionalDependencies:
|
||||
vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)
|
||||
|
||||
'@vitest/mocker@3.2.4(vite@6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0))':
|
||||
dependencies:
|
||||
'@vitest/spy': 3.2.4
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.21
|
||||
optionalDependencies:
|
||||
vite: 6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)
|
||||
vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)
|
||||
|
||||
'@vitest/pretty-format@3.2.4':
|
||||
dependencies:
|
||||
@@ -3076,13 +3056,13 @@ snapshots:
|
||||
'@types/unist': 3.0.3
|
||||
vfile-message: 4.0.3
|
||||
|
||||
vite-node@3.2.4(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0):
|
||||
vite-node@3.2.4(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
debug: 4.4.3
|
||||
es-module-lexer: 1.7.0
|
||||
pathe: 2.0.3
|
||||
vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)
|
||||
vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- jiti
|
||||
@@ -3097,13 +3077,13 @@ snapshots:
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
vite-node@3.2.4(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0):
|
||||
vite-node@3.2.4(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
debug: 4.4.3
|
||||
es-module-lexer: 1.7.0
|
||||
pathe: 2.0.3
|
||||
vite: 6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)
|
||||
vite: 6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- jiti
|
||||
@@ -3118,7 +3098,7 @@ snapshots:
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0):
|
||||
vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3):
|
||||
dependencies:
|
||||
esbuild: 0.25.12
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
@@ -3131,8 +3111,9 @@ snapshots:
|
||||
fsevents: 2.3.3
|
||||
jiti: 2.6.1
|
||||
lightningcss: 1.32.0
|
||||
yaml: 2.8.3
|
||||
|
||||
vite@6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0):
|
||||
vite@6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3):
|
||||
dependencies:
|
||||
esbuild: 0.25.12
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
@@ -3145,12 +3126,13 @@ snapshots:
|
||||
fsevents: 2.3.3
|
||||
jiti: 2.6.1
|
||||
lightningcss: 1.32.0
|
||||
yaml: 2.8.3
|
||||
|
||||
vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0):
|
||||
vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3):
|
||||
dependencies:
|
||||
'@types/chai': 5.2.3
|
||||
'@vitest/expect': 3.2.4
|
||||
'@vitest/mocker': 3.2.4(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0))
|
||||
'@vitest/mocker': 3.2.4(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3))
|
||||
'@vitest/pretty-format': 3.2.4
|
||||
'@vitest/runner': 3.2.4
|
||||
'@vitest/snapshot': 3.2.4
|
||||
@@ -3168,8 +3150,8 @@ snapshots:
|
||||
tinyglobby: 0.2.15
|
||||
tinypool: 1.1.1
|
||||
tinyrainbow: 2.0.0
|
||||
vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)
|
||||
vite-node: 3.2.4(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)
|
||||
vite: 6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)
|
||||
vite-node: 3.2.4(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@types/debug': 4.1.13
|
||||
@@ -3188,11 +3170,11 @@ snapshots:
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
vitest@3.2.4(@types/debug@4.1.13)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0):
|
||||
vitest@3.2.4(@types/debug@4.1.13)(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3):
|
||||
dependencies:
|
||||
'@types/chai': 5.2.3
|
||||
'@vitest/expect': 3.2.4
|
||||
'@vitest/mocker': 3.2.4(vite@6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0))
|
||||
'@vitest/mocker': 3.2.4(vite@6.4.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3))
|
||||
'@vitest/pretty-format': 3.2.4
|
||||
'@vitest/runner': 3.2.4
|
||||
'@vitest/snapshot': 3.2.4
|
||||
@@ -3210,8 +3192,8 @@ snapshots:
|
||||
tinyglobby: 0.2.15
|
||||
tinypool: 1.1.1
|
||||
tinyrainbow: 2.0.0
|
||||
vite: 6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)
|
||||
vite-node: 3.2.4(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)
|
||||
vite: 6.4.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)
|
||||
vite-node: 3.2.4(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.32.0)(yaml@2.8.3)
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@types/debug': 4.1.13
|
||||
@@ -3253,6 +3235,8 @@ snapshots:
|
||||
|
||||
yallist@3.1.1: {}
|
||||
|
||||
yaml@2.8.3: {}
|
||||
|
||||
zustand@4.5.7(@types/react@19.2.14)(react@19.2.4):
|
||||
dependencies:
|
||||
use-sync-external-store: 1.6.0(react@19.2.4)
|
||||
|
||||
Reference in New Issue
Block a user