diff --git a/.gitignore b/.gitignore index 7c154ae1..dbb92822 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,6 @@ # Dependencies node_modules/ -# Build output -dist/ - .cmem # IDE diff --git a/README.md b/README.md index f8f39e97..e0c08ece 100644 --- a/README.md +++ b/README.md @@ -19,11 +19,9 @@ ### Get Started ```bash -npx @colbymchenry/codegraph +npm install -g https://github.com/Quon/codegraph/archive/refs/heads/main.tar.gz && codegraph install ``` -Interactive installer configures Claude Code automatically - #### Initialize Projects ```bash @@ -145,7 +143,7 @@ CodeGraph detects web-framework routing files and emits `route` nodes linked by ### 1. Run the Installer ```bash -npx @colbymchenry/codegraph +npm install -g https://github.com/Quon/codegraph/archive/refs/heads/main.tar.gz && codegraph install ``` The installer will: @@ -173,7 +171,7 @@ That's it! Claude Code will use CodeGraph tools automatically when a `.codegraph **Install globally:** ```bash -npm install -g @colbymchenry/codegraph +npm install -g https://github.com/Quon/codegraph/archive/refs/heads/main.tar.gz ``` **Add to `~/.claude.json`:** @@ -311,8 +309,36 @@ codegraph files [path] # Show file structure (--format, --filter, --m codegraph context # Build context for AI (--format, --max-nodes) codegraph affected [files...] # Find test files affected by changes (see below) codegraph serve --mcp # Start MCP server + +# Monorepo sub-projects +codegraph project list # List registered sub-projects +codegraph project add # Register a sub-project (relative path from root) +codegraph project remove # Unregister a sub-project +codegraph project scan # Auto-discover initialized sub-projects (--depth, default 3) ``` +### Monorepo / multi-project + +Initialize each sub-project independently, then register them from the monorepo root so the MCP tools can address them by name. + +```bash +# In each sub-project +cd packages/foo && codegraph init -i && codegraph index + +# Back at the root — auto-discover all initialized sub-projects +cd ../.. +codegraph project scan # writes .codegraph/projects.json + +# Or register manually +codegraph project add packages/foo +codegraph project add packages/bar + +# Verify +codegraph project list +``` + +MCP tools accept a `project` parameter (`"packages/foo"`) to target a specific sub-project. + ### `codegraph affected` Traces import dependencies transitively to find which test files are affected by changed source files. diff --git a/__tests__/installer.test.ts b/__tests__/installer.test.ts index e2e24d1c..2c63fd94 100644 --- a/__tests__/installer.test.ts +++ b/__tests__/installer.test.ts @@ -88,6 +88,29 @@ describe('Installer Config Writer', () => { warnSpy.mockRestore(); }); + it('should use npx-based command for MCP server (no global install required)', () => { + writeMcpConfig('local'); + + const claudeJson = path.join(tempDir, '.claude.json'); + const content = JSON.parse(fs.readFileSync(claudeJson, 'utf-8')); + const server = content.mcpServers.codegraph; + + expect(server.type).toBe('stdio'); + // Should use npx, not bare 'codegraph' command + if (process.platform === 'win32') { + expect(server.command).toBe('cmd'); + expect(server.args[0]).toBe('/c'); + expect(server.args[1]).toBe('npx'); + } else { + expect(server.command).toBe('npx'); + } + // Should reference the GitHub package + const args = server.args as string[]; + expect(args.some((a: string) => a.includes('github:Quon/codegraph'))).toBe(true); + expect(args.some((a: string) => a === 'serve')).toBe(true); + expect(args.some((a: string) => a === '--mcp')).toBe(true); + }); + it('should preserve existing valid config when adding codegraph', () => { const claudeJson = path.join(tempDir, '.claude.json'); fs.writeFileSync(claudeJson, JSON.stringify({ diff --git a/__tests__/node-version-check.test.ts b/__tests__/node-version-check.test.ts index d7b725cb..dc1c6dda 100644 --- a/__tests__/node-version-check.test.ts +++ b/__tests__/node-version-check.test.ts @@ -7,36 +7,36 @@ */ import { describe, it, expect } from 'vitest'; -import { buildNode25BlockBanner } from '../src/bin/node-version-check'; +import { buildNodeBlockBanner } from '../src/bin/node-version-check'; -describe('buildNode25BlockBanner', () => { +describe('buildNodeBlockBanner', () => { it('embeds the reported Node version in the header', () => { - expect(buildNode25BlockBanner('25.9.0')).toContain( + expect(buildNodeBlockBanner('25.9.0')).toContain( 'Unsupported Node.js version: 25.9.0' ); }); it('names the V8 turboshaft WASM root cause and the OOM symptom', () => { - const banner = buildNode25BlockBanner('25.7.0'); + const banner = buildNodeBlockBanner('25.7.0'); expect(banner).toContain('V8 WASM JIT'); expect(banner).toContain('turboshaft'); expect(banner).toContain('Fatal process out of memory: Zone'); }); it('points users to Node 22 LTS via nvm and Homebrew', () => { - const banner = buildNode25BlockBanner('25.7.0'); + const banner = buildNodeBlockBanner('25.7.0'); expect(banner).toContain('Node.js 22 LTS'); expect(banner).toContain('nvm install 22'); expect(banner).toContain('brew install node@22'); }); it('documents the CODEGRAPH_ALLOW_UNSAFE_NODE override', () => { - const banner = buildNode25BlockBanner('25.7.0'); + const banner = buildNodeBlockBanner('25.7.0'); expect(banner).toContain('CODEGRAPH_ALLOW_UNSAFE_NODE=1'); }); it('links to issue #81 for the root-cause writeup', () => { - expect(buildNode25BlockBanner('25.7.0')).toContain( + expect(buildNodeBlockBanner('25.7.0')).toContain( 'github.com/colbymchenry/codegraph/issues/81' ); }); diff --git a/__tests__/projects.test.ts b/__tests__/projects.test.ts new file mode 100644 index 00000000..8267ce2c --- /dev/null +++ b/__tests__/projects.test.ts @@ -0,0 +1,293 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; +import { + PROJECTS_FILENAME, + getProjectsPath, + loadProjects, + saveProjects, + addProject, + removeProject, + scanForProjects, + syncProjects, +} from '../src/projects'; +import { CodeGraph } from '../src'; + +function createTempDir(): string { + return fs.mkdtempSync(path.join(os.tmpdir(), 'monorepo-test-')); +} + +function cleanupTempDir(dir: string): void { + if (fs.existsSync(dir)) { + fs.rmSync(dir, { recursive: true, force: true }); + } +} + +function createProject(root: string, sub: string): void { + const dir = path.join(root, sub); + fs.mkdirSync(dir, { recursive: true }); + // Create a minimal .codegraph/codegraph.db to satisfy isInitialized() + const cgDir = path.join(dir, '.codegraph'); + fs.mkdirSync(cgDir, { recursive: true }); + fs.writeFileSync(path.join(cgDir, 'codegraph.db'), '', 'utf-8'); +} + +describe('Projects Registry', () => { + let tempDir: string; + + beforeEach(() => { + tempDir = createTempDir(); + }); + + afterEach(() => { + cleanupTempDir(tempDir); + }); + + describe('getProjectsPath', () => { + it('should return correct path to projects.json', () => { + expect(getProjectsPath(tempDir)).toBe( + path.join(tempDir, '.codegraph', 'projects.json') + ); + }); + }); + + describe('loadProjects', () => { + it('should return empty array when projects.json does not exist', () => { + const result = loadProjects(tempDir); + expect(result).toEqual([]); + }); + + it('should return empty array when projects.json has invalid JSON', () => { + const cgDir = path.join(tempDir, '.codegraph'); + fs.mkdirSync(cgDir, { recursive: true }); + fs.writeFileSync(path.join(cgDir, PROJECTS_FILENAME), 'not json', 'utf-8'); + const result = loadProjects(tempDir); + expect(result).toEqual([]); + }); + + it('should load projects from projects.json', () => { + const cgDir = path.join(tempDir, '.codegraph'); + fs.mkdirSync(cgDir, { recursive: true }); + fs.writeFileSync( + path.join(cgDir, PROJECTS_FILENAME), + JSON.stringify(['packages/foo', 'packages/bar']), + 'utf-8' + ); + const result = loadProjects(tempDir); + expect(result).toEqual(['packages/foo', 'packages/bar']); + }); + + it('should filter non-string entries', () => { + const cgDir = path.join(tempDir, '.codegraph'); + fs.mkdirSync(cgDir, { recursive: true }); + fs.writeFileSync( + path.join(cgDir, PROJECTS_FILENAME), + JSON.stringify(['valid', 42, null, { bad: true }]), + 'utf-8' + ); + const result = loadProjects(tempDir); + expect(result).toEqual(['valid']); + }); + }); + + describe('saveProjects', () => { + it('should save projects to projects.json', () => { + saveProjects(tempDir, ['packages/bar', 'packages/foo']); + const cgDir = path.join(tempDir, '.codegraph'); + const content = fs.readFileSync(path.join(cgDir, PROJECTS_FILENAME), 'utf-8'); + expect(JSON.parse(content)).toEqual(['packages/bar', 'packages/foo']); + }); + + it('should write atomically (temp file + rename)', () => { + saveProjects(tempDir, ['packages/foo']); + const cgDir = path.join(tempDir, '.codegraph'); + // No .tmp file should remain after write + const files = fs.readdirSync(cgDir); + expect(files.filter(f => f.endsWith('.tmp'))).toEqual([]); + }); + }); + + describe('addProject', () => { + it('should add a project to the registry', () => { + const result = addProject(tempDir, 'packages/foo'); + expect(result).toEqual(['packages/foo']); + // Verify persisted + expect(loadProjects(tempDir)).toEqual(['packages/foo']); + }); + + it('should deduplicate when adding existing project', () => { + addProject(tempDir, 'packages/foo'); + const result = addProject(tempDir, 'packages/foo'); + expect(result).toEqual(['packages/foo']); + }); + + it('should append to existing projects', () => { + addProject(tempDir, 'packages/foo'); + const result = addProject(tempDir, 'packages/bar'); + // Return value preserves insertion order; file on disk is sorted + expect(result).toContain('packages/foo'); + expect(result).toContain('packages/bar'); + }); + }); + + describe('removeProject', () => { + it('should remove a project from the registry', () => { + addProject(tempDir, 'packages/foo'); + addProject(tempDir, 'packages/bar'); + const result = removeProject(tempDir, 'packages/foo'); + expect(result).toEqual(['packages/bar']); + }); + + it('should do nothing when removing non-existent project', () => { + addProject(tempDir, 'packages/foo'); + const result = removeProject(tempDir, 'packages/nonexistent'); + expect(result).toEqual(['packages/foo']); + }); + }); + + describe('scanForProjects', () => { + it('should discover initialized sub-projects', () => { + createProject(tempDir, 'packages/foo'); + createProject(tempDir, 'packages/bar'); + const result = scanForProjects(tempDir); + expect(result).toEqual(['packages/bar', 'packages/foo']); + }); + + it('should respect maxDepth', () => { + createProject(tempDir, 'packages/foo'); + createProject(tempDir, 'packages/group/deep'); + const result = scanForProjects(tempDir, 1); + expect(result).toEqual(['packages/foo']); + }); + + it('should skip .codegraph/, .git/, node_modules/', () => { + createProject(tempDir, '.codegraph'); // <-- inside root .codegraph + createProject(tempDir, 'node_modules/pkg'); + createProject(tempDir, '.git/hooks'); + createProject(tempDir, 'packages/real'); + const result = scanForProjects(tempDir); + expect(result).toEqual(['packages/real']); + }); + + it('should return empty array when no sub-projects exist', () => { + const result = scanForProjects(tempDir); + expect(result).toEqual([]); + }); + }); + + describe('syncProjects', () => { + it('should merge discovered projects with existing registry', () => { + // Pre-register a project that scan will not find + addProject(tempDir, 'legacy/manual'); + // Create discoverable projects + createProject(tempDir, 'packages/foo'); + const result = syncProjects(tempDir); + expect(result).toContain('legacy/manual'); + expect(result).toContain('packages/foo'); + }); + }); +}); + +// ─────────────────────────────────────────────────────── +// Integration tests — these go AFTER the existing tests +// ─────────────────────────────────────────────────────── + +describe('Monorepo Integration', () => { + let tempDir: string; + + beforeEach(() => { + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'monorepo-int-')); + }); + + afterEach(() => { + if (fs.existsSync(tempDir)) { + fs.rmSync(tempDir, { recursive: true, force: true }); + } + }); + + it('should init sub-projects and register them', () => { + // Create monorepo structure + const packagesFoo = path.join(tempDir, 'packages/foo'); + const packagesBar = path.join(tempDir, 'packages/bar'); + fs.mkdirSync(packagesFoo, { recursive: true }); + fs.mkdirSync(packagesBar, { recursive: true }); + + // Init root project + const rootCg = CodeGraph.initSync(tempDir); + rootCg.close(); + + // Init sub-projects + const fooCg = CodeGraph.initSync(packagesFoo); + fooCg.close(); + const barCg = CodeGraph.initSync(packagesBar); + barCg.close(); + + // Register sub-projects + addProject(tempDir, 'packages/foo'); + addProject(tempDir, 'packages/bar'); + + // Verify + const projects = loadProjects(tempDir); + expect(projects).toContain('packages/foo'); + expect(projects).toContain('packages/bar'); + }); + + it('scanForProjects should find sub-projects created after init', () => { + // Init root + const rootCg = CodeGraph.initSync(tempDir); + rootCg.close(); + + // No sub-projects yet + expect(scanForProjects(tempDir)).toEqual([]); + + // Create and init a sub-project + const sub = path.join(tempDir, 'packages/new'); + fs.mkdirSync(sub, { recursive: true }); + const subCg = CodeGraph.initSync(sub); + subCg.close(); + + // Scan should find it + const found = scanForProjects(tempDir); + expect(found).toContain('packages/new'); + }); + + it('syncProjects preserves manually added projects during scan', () => { + // Init root + const rootCg = CodeGraph.initSync(tempDir); + rootCg.close(); + + // Add a manual entry (that won't be found by scan) + addProject(tempDir, 'manual/pkg'); + // Create and init an auto-discoverable project + const sub = path.join(tempDir, 'packages/auto'); + fs.mkdirSync(sub, { recursive: true }); + const subCg = CodeGraph.initSync(sub); + subCg.close(); + + // sync + const merged = syncProjects(tempDir); + expect(merged).toContain('manual/pkg'); + expect(merged).toContain('packages/auto'); + }); + + it('should handle project add/remove lifecycle', () => { + const rootCg = CodeGraph.initSync(tempDir); + rootCg.close(); + + addProject(tempDir, 'packages/foo'); + expect(loadProjects(tempDir)).toEqual(['packages/foo']); + + removeProject(tempDir, 'packages/foo'); + expect(loadProjects(tempDir)).toEqual([]); + }); + + it('should handle corrupted projects.json gracefully', () => { + const cgDir = path.join(tempDir, '.codegraph'); + fs.mkdirSync(cgDir, { recursive: true }); + fs.writeFileSync(path.join(cgDir, 'projects.json'), '{broken json', 'utf-8'); + + const result = loadProjects(tempDir); + expect(result).toEqual([]); + }); +}); diff --git a/dist/bin/codegraph.d.ts b/dist/bin/codegraph.d.ts new file mode 100644 index 00000000..f3b15d9c --- /dev/null +++ b/dist/bin/codegraph.d.ts @@ -0,0 +1,21 @@ +#!/usr/bin/env node +/** + * CodeGraph CLI + * + * Command-line interface for CodeGraph code intelligence. + * + * Usage: + * codegraph Run interactive installer (when no args) + * codegraph install Run interactive installer + * codegraph init [path] Initialize CodeGraph in a project + * codegraph uninit [path] Remove CodeGraph from a project + * codegraph index [path] Index all files in the project + * codegraph sync [path] Sync changes since last index + * codegraph status [path] Show index status + * codegraph query Search for symbols + * codegraph files [options] Show project file structure + * codegraph context Build context for a task + * codegraph affected [files] Find test files affected by changes + */ +export {}; +//# sourceMappingURL=codegraph.d.ts.map \ No newline at end of file diff --git a/dist/bin/codegraph.d.ts.map b/dist/bin/codegraph.d.ts.map new file mode 100644 index 00000000..2f349cb4 --- /dev/null +++ b/dist/bin/codegraph.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"codegraph.d.ts","sourceRoot":"","sources":["../../src/bin/codegraph.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;GAiBG"} \ No newline at end of file diff --git a/dist/bin/codegraph.js b/dist/bin/codegraph.js new file mode 100755 index 00000000..5e37fb9a --- /dev/null +++ b/dist/bin/codegraph.js @@ -0,0 +1,24670 @@ +#!/usr/bin/env node +"use strict"; +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __esm = (fn, res) => function __init() { + return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; +}; +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); + +// node_modules/commander/lib/error.js +var require_error = __commonJS({ + "node_modules/commander/lib/error.js"(exports2) { + var CommanderError2 = class extends Error { + /** + * Constructs the CommanderError class + * @param {number} exitCode suggested exit code which could be used with process.exit + * @param {string} code an id string representing the error + * @param {string} message human-readable description of the error + */ + constructor(exitCode, code, message) { + super(message); + Error.captureStackTrace(this, this.constructor); + this.name = this.constructor.name; + this.code = code; + this.exitCode = exitCode; + this.nestedError = void 0; + } + }; + var InvalidArgumentError2 = class extends CommanderError2 { + /** + * Constructs the InvalidArgumentError class + * @param {string} [message] explanation of why argument is invalid + */ + constructor(message) { + super(1, "commander.invalidArgument", message); + Error.captureStackTrace(this, this.constructor); + this.name = this.constructor.name; + } + }; + exports2.CommanderError = CommanderError2; + exports2.InvalidArgumentError = InvalidArgumentError2; + } +}); + +// node_modules/commander/lib/argument.js +var require_argument = __commonJS({ + "node_modules/commander/lib/argument.js"(exports2) { + var { InvalidArgumentError: InvalidArgumentError2 } = require_error(); + var Argument2 = class { + /** + * Initialize a new command argument with the given name and description. + * The default is that the argument is required, and you can explicitly + * indicate this with <> around the name. Put [] around the name for an optional argument. + * + * @param {string} name + * @param {string} [description] + */ + constructor(name, description) { + this.description = description || ""; + this.variadic = false; + this.parseArg = void 0; + this.defaultValue = void 0; + this.defaultValueDescription = void 0; + this.argChoices = void 0; + switch (name[0]) { + case "<": + this.required = true; + this._name = name.slice(1, -1); + break; + case "[": + this.required = false; + this._name = name.slice(1, -1); + break; + default: + this.required = true; + this._name = name; + break; + } + if (this._name.endsWith("...")) { + this.variadic = true; + this._name = this._name.slice(0, -3); + } + } + /** + * Return argument name. + * + * @return {string} + */ + name() { + return this._name; + } + /** + * @package + */ + _collectValue(value, previous) { + if (previous === this.defaultValue || !Array.isArray(previous)) { + return [value]; + } + previous.push(value); + return previous; + } + /** + * Set the default value, and optionally supply the description to be displayed in the help. + * + * @param {*} value + * @param {string} [description] + * @return {Argument} + */ + default(value, description) { + this.defaultValue = value; + this.defaultValueDescription = description; + return this; + } + /** + * Set the custom handler for processing CLI command arguments into argument values. + * + * @param {Function} [fn] + * @return {Argument} + */ + argParser(fn) { + this.parseArg = fn; + return this; + } + /** + * Only allow argument value to be one of choices. + * + * @param {string[]} values + * @return {Argument} + */ + choices(values) { + this.argChoices = values.slice(); + this.parseArg = (arg, previous) => { + if (!this.argChoices.includes(arg)) { + throw new InvalidArgumentError2( + `Allowed choices are ${this.argChoices.join(", ")}.` + ); + } + if (this.variadic) { + return this._collectValue(arg, previous); + } + return arg; + }; + return this; + } + /** + * Make argument required. + * + * @returns {Argument} + */ + argRequired() { + this.required = true; + return this; + } + /** + * Make argument optional. + * + * @returns {Argument} + */ + argOptional() { + this.required = false; + return this; + } + }; + function humanReadableArgName(arg) { + const nameOutput = arg.name() + (arg.variadic === true ? "..." : ""); + return arg.required ? "<" + nameOutput + ">" : "[" + nameOutput + "]"; + } + exports2.Argument = Argument2; + exports2.humanReadableArgName = humanReadableArgName; + } +}); + +// node_modules/commander/lib/help.js +var require_help = __commonJS({ + "node_modules/commander/lib/help.js"(exports2) { + var { humanReadableArgName } = require_argument(); + var Help2 = class { + constructor() { + this.helpWidth = void 0; + this.minWidthToWrap = 40; + this.sortSubcommands = false; + this.sortOptions = false; + this.showGlobalOptions = false; + } + /** + * prepareContext is called by Commander after applying overrides from `Command.configureHelp()` + * and just before calling `formatHelp()`. + * + * Commander just uses the helpWidth and the rest is provided for optional use by more complex subclasses. + * + * @param {{ error?: boolean, helpWidth?: number, outputHasColors?: boolean }} contextOptions + */ + prepareContext(contextOptions) { + this.helpWidth = this.helpWidth ?? contextOptions.helpWidth ?? 80; + } + /** + * Get an array of the visible subcommands. Includes a placeholder for the implicit help command, if there is one. + * + * @param {Command} cmd + * @returns {Command[]} + */ + visibleCommands(cmd) { + const visibleCommands = cmd.commands.filter((cmd2) => !cmd2._hidden); + const helpCommand = cmd._getHelpCommand(); + if (helpCommand && !helpCommand._hidden) { + visibleCommands.push(helpCommand); + } + if (this.sortSubcommands) { + visibleCommands.sort((a, b) => { + return a.name().localeCompare(b.name()); + }); + } + return visibleCommands; + } + /** + * Compare options for sort. + * + * @param {Option} a + * @param {Option} b + * @returns {number} + */ + compareOptions(a, b) { + const getSortKey = (option) => { + return option.short ? option.short.replace(/^-/, "") : option.long.replace(/^--/, ""); + }; + return getSortKey(a).localeCompare(getSortKey(b)); + } + /** + * Get an array of the visible options. Includes a placeholder for the implicit help option, if there is one. + * + * @param {Command} cmd + * @returns {Option[]} + */ + visibleOptions(cmd) { + const visibleOptions = cmd.options.filter((option) => !option.hidden); + const helpOption = cmd._getHelpOption(); + if (helpOption && !helpOption.hidden) { + const removeShort = helpOption.short && cmd._findOption(helpOption.short); + const removeLong = helpOption.long && cmd._findOption(helpOption.long); + if (!removeShort && !removeLong) { + visibleOptions.push(helpOption); + } else if (helpOption.long && !removeLong) { + visibleOptions.push( + cmd.createOption(helpOption.long, helpOption.description) + ); + } else if (helpOption.short && !removeShort) { + visibleOptions.push( + cmd.createOption(helpOption.short, helpOption.description) + ); + } + } + if (this.sortOptions) { + visibleOptions.sort(this.compareOptions); + } + return visibleOptions; + } + /** + * Get an array of the visible global options. (Not including help.) + * + * @param {Command} cmd + * @returns {Option[]} + */ + visibleGlobalOptions(cmd) { + if (!this.showGlobalOptions) return []; + const globalOptions = []; + for (let ancestorCmd = cmd.parent; ancestorCmd; ancestorCmd = ancestorCmd.parent) { + const visibleOptions = ancestorCmd.options.filter( + (option) => !option.hidden + ); + globalOptions.push(...visibleOptions); + } + if (this.sortOptions) { + globalOptions.sort(this.compareOptions); + } + return globalOptions; + } + /** + * Get an array of the arguments if any have a description. + * + * @param {Command} cmd + * @returns {Argument[]} + */ + visibleArguments(cmd) { + if (cmd._argsDescription) { + cmd.registeredArguments.forEach((argument) => { + argument.description = argument.description || cmd._argsDescription[argument.name()] || ""; + }); + } + if (cmd.registeredArguments.find((argument) => argument.description)) { + return cmd.registeredArguments; + } + return []; + } + /** + * Get the command term to show in the list of subcommands. + * + * @param {Command} cmd + * @returns {string} + */ + subcommandTerm(cmd) { + const args = cmd.registeredArguments.map((arg) => humanReadableArgName(arg)).join(" "); + return cmd._name + (cmd._aliases[0] ? "|" + cmd._aliases[0] : "") + (cmd.options.length ? " [options]" : "") + // simplistic check for non-help option + (args ? " " + args : ""); + } + /** + * Get the option term to show in the list of options. + * + * @param {Option} option + * @returns {string} + */ + optionTerm(option) { + return option.flags; + } + /** + * Get the argument term to show in the list of arguments. + * + * @param {Argument} argument + * @returns {string} + */ + argumentTerm(argument) { + return argument.name(); + } + /** + * Get the longest command term length. + * + * @param {Command} cmd + * @param {Help} helper + * @returns {number} + */ + longestSubcommandTermLength(cmd, helper) { + return helper.visibleCommands(cmd).reduce((max, command) => { + return Math.max( + max, + this.displayWidth( + helper.styleSubcommandTerm(helper.subcommandTerm(command)) + ) + ); + }, 0); + } + /** + * Get the longest option term length. + * + * @param {Command} cmd + * @param {Help} helper + * @returns {number} + */ + longestOptionTermLength(cmd, helper) { + return helper.visibleOptions(cmd).reduce((max, option) => { + return Math.max( + max, + this.displayWidth(helper.styleOptionTerm(helper.optionTerm(option))) + ); + }, 0); + } + /** + * Get the longest global option term length. + * + * @param {Command} cmd + * @param {Help} helper + * @returns {number} + */ + longestGlobalOptionTermLength(cmd, helper) { + return helper.visibleGlobalOptions(cmd).reduce((max, option) => { + return Math.max( + max, + this.displayWidth(helper.styleOptionTerm(helper.optionTerm(option))) + ); + }, 0); + } + /** + * Get the longest argument term length. + * + * @param {Command} cmd + * @param {Help} helper + * @returns {number} + */ + longestArgumentTermLength(cmd, helper) { + return helper.visibleArguments(cmd).reduce((max, argument) => { + return Math.max( + max, + this.displayWidth( + helper.styleArgumentTerm(helper.argumentTerm(argument)) + ) + ); + }, 0); + } + /** + * Get the command usage to be displayed at the top of the built-in help. + * + * @param {Command} cmd + * @returns {string} + */ + commandUsage(cmd) { + let cmdName = cmd._name; + if (cmd._aliases[0]) { + cmdName = cmdName + "|" + cmd._aliases[0]; + } + let ancestorCmdNames = ""; + for (let ancestorCmd = cmd.parent; ancestorCmd; ancestorCmd = ancestorCmd.parent) { + ancestorCmdNames = ancestorCmd.name() + " " + ancestorCmdNames; + } + return ancestorCmdNames + cmdName + " " + cmd.usage(); + } + /** + * Get the description for the command. + * + * @param {Command} cmd + * @returns {string} + */ + commandDescription(cmd) { + return cmd.description(); + } + /** + * Get the subcommand summary to show in the list of subcommands. + * (Fallback to description for backwards compatibility.) + * + * @param {Command} cmd + * @returns {string} + */ + subcommandDescription(cmd) { + return cmd.summary() || cmd.description(); + } + /** + * Get the option description to show in the list of options. + * + * @param {Option} option + * @return {string} + */ + optionDescription(option) { + const extraInfo = []; + if (option.argChoices) { + extraInfo.push( + // use stringify to match the display of the default value + `choices: ${option.argChoices.map((choice) => JSON.stringify(choice)).join(", ")}` + ); + } + if (option.defaultValue !== void 0) { + const showDefault = option.required || option.optional || option.isBoolean() && typeof option.defaultValue === "boolean"; + if (showDefault) { + extraInfo.push( + `default: ${option.defaultValueDescription || JSON.stringify(option.defaultValue)}` + ); + } + } + if (option.presetArg !== void 0 && option.optional) { + extraInfo.push(`preset: ${JSON.stringify(option.presetArg)}`); + } + if (option.envVar !== void 0) { + extraInfo.push(`env: ${option.envVar}`); + } + if (extraInfo.length > 0) { + const extraDescription = `(${extraInfo.join(", ")})`; + if (option.description) { + return `${option.description} ${extraDescription}`; + } + return extraDescription; + } + return option.description; + } + /** + * Get the argument description to show in the list of arguments. + * + * @param {Argument} argument + * @return {string} + */ + argumentDescription(argument) { + const extraInfo = []; + if (argument.argChoices) { + extraInfo.push( + // use stringify to match the display of the default value + `choices: ${argument.argChoices.map((choice) => JSON.stringify(choice)).join(", ")}` + ); + } + if (argument.defaultValue !== void 0) { + extraInfo.push( + `default: ${argument.defaultValueDescription || JSON.stringify(argument.defaultValue)}` + ); + } + if (extraInfo.length > 0) { + const extraDescription = `(${extraInfo.join(", ")})`; + if (argument.description) { + return `${argument.description} ${extraDescription}`; + } + return extraDescription; + } + return argument.description; + } + /** + * Format a list of items, given a heading and an array of formatted items. + * + * @param {string} heading + * @param {string[]} items + * @param {Help} helper + * @returns string[] + */ + formatItemList(heading, items, helper) { + if (items.length === 0) return []; + return [helper.styleTitle(heading), ...items, ""]; + } + /** + * Group items by their help group heading. + * + * @param {Command[] | Option[]} unsortedItems + * @param {Command[] | Option[]} visibleItems + * @param {Function} getGroup + * @returns {Map} + */ + groupItems(unsortedItems, visibleItems, getGroup) { + const result = /* @__PURE__ */ new Map(); + unsortedItems.forEach((item) => { + const group = getGroup(item); + if (!result.has(group)) result.set(group, []); + }); + visibleItems.forEach((item) => { + const group = getGroup(item); + if (!result.has(group)) { + result.set(group, []); + } + result.get(group).push(item); + }); + return result; + } + /** + * Generate the built-in help text. + * + * @param {Command} cmd + * @param {Help} helper + * @returns {string} + */ + formatHelp(cmd, helper) { + const termWidth = helper.padWidth(cmd, helper); + const helpWidth = helper.helpWidth ?? 80; + function callFormatItem(term, description) { + return helper.formatItem(term, termWidth, description, helper); + } + let output = [ + `${helper.styleTitle("Usage:")} ${helper.styleUsage(helper.commandUsage(cmd))}`, + "" + ]; + const commandDescription = helper.commandDescription(cmd); + if (commandDescription.length > 0) { + output = output.concat([ + helper.boxWrap( + helper.styleCommandDescription(commandDescription), + helpWidth + ), + "" + ]); + } + const argumentList = helper.visibleArguments(cmd).map((argument) => { + return callFormatItem( + helper.styleArgumentTerm(helper.argumentTerm(argument)), + helper.styleArgumentDescription(helper.argumentDescription(argument)) + ); + }); + output = output.concat( + this.formatItemList("Arguments:", argumentList, helper) + ); + const optionGroups = this.groupItems( + cmd.options, + helper.visibleOptions(cmd), + (option) => option.helpGroupHeading ?? "Options:" + ); + optionGroups.forEach((options, group) => { + const optionList = options.map((option) => { + return callFormatItem( + helper.styleOptionTerm(helper.optionTerm(option)), + helper.styleOptionDescription(helper.optionDescription(option)) + ); + }); + output = output.concat(this.formatItemList(group, optionList, helper)); + }); + if (helper.showGlobalOptions) { + const globalOptionList = helper.visibleGlobalOptions(cmd).map((option) => { + return callFormatItem( + helper.styleOptionTerm(helper.optionTerm(option)), + helper.styleOptionDescription(helper.optionDescription(option)) + ); + }); + output = output.concat( + this.formatItemList("Global Options:", globalOptionList, helper) + ); + } + const commandGroups = this.groupItems( + cmd.commands, + helper.visibleCommands(cmd), + (sub) => sub.helpGroup() || "Commands:" + ); + commandGroups.forEach((commands, group) => { + const commandList = commands.map((sub) => { + return callFormatItem( + helper.styleSubcommandTerm(helper.subcommandTerm(sub)), + helper.styleSubcommandDescription(helper.subcommandDescription(sub)) + ); + }); + output = output.concat(this.formatItemList(group, commandList, helper)); + }); + return output.join("\n"); + } + /** + * Return display width of string, ignoring ANSI escape sequences. Used in padding and wrapping calculations. + * + * @param {string} str + * @returns {number} + */ + displayWidth(str) { + return stripColor(str).length; + } + /** + * Style the title for displaying in the help. Called with 'Usage:', 'Options:', etc. + * + * @param {string} str + * @returns {string} + */ + styleTitle(str) { + return str; + } + styleUsage(str) { + return str.split(" ").map((word) => { + if (word === "[options]") return this.styleOptionText(word); + if (word === "[command]") return this.styleSubcommandText(word); + if (word[0] === "[" || word[0] === "<") + return this.styleArgumentText(word); + return this.styleCommandText(word); + }).join(" "); + } + styleCommandDescription(str) { + return this.styleDescriptionText(str); + } + styleOptionDescription(str) { + return this.styleDescriptionText(str); + } + styleSubcommandDescription(str) { + return this.styleDescriptionText(str); + } + styleArgumentDescription(str) { + return this.styleDescriptionText(str); + } + styleDescriptionText(str) { + return str; + } + styleOptionTerm(str) { + return this.styleOptionText(str); + } + styleSubcommandTerm(str) { + return str.split(" ").map((word) => { + if (word === "[options]") return this.styleOptionText(word); + if (word[0] === "[" || word[0] === "<") + return this.styleArgumentText(word); + return this.styleSubcommandText(word); + }).join(" "); + } + styleArgumentTerm(str) { + return this.styleArgumentText(str); + } + styleOptionText(str) { + return str; + } + styleArgumentText(str) { + return str; + } + styleSubcommandText(str) { + return str; + } + styleCommandText(str) { + return str; + } + /** + * Calculate the pad width from the maximum term length. + * + * @param {Command} cmd + * @param {Help} helper + * @returns {number} + */ + padWidth(cmd, helper) { + return Math.max( + helper.longestOptionTermLength(cmd, helper), + helper.longestGlobalOptionTermLength(cmd, helper), + helper.longestSubcommandTermLength(cmd, helper), + helper.longestArgumentTermLength(cmd, helper) + ); + } + /** + * Detect manually wrapped and indented strings by checking for line break followed by whitespace. + * + * @param {string} str + * @returns {boolean} + */ + preformatted(str) { + return /\n[^\S\r\n]/.test(str); + } + /** + * Format the "item", which consists of a term and description. Pad the term and wrap the description, indenting the following lines. + * + * So "TTT", 5, "DDD DDDD DD DDD" might be formatted for this.helpWidth=17 like so: + * TTT DDD DDDD + * DD DDD + * + * @param {string} term + * @param {number} termWidth + * @param {string} description + * @param {Help} helper + * @returns {string} + */ + formatItem(term, termWidth, description, helper) { + const itemIndent = 2; + const itemIndentStr = " ".repeat(itemIndent); + if (!description) return itemIndentStr + term; + const paddedTerm = term.padEnd( + termWidth + term.length - helper.displayWidth(term) + ); + const spacerWidth = 2; + const helpWidth = this.helpWidth ?? 80; + const remainingWidth = helpWidth - termWidth - spacerWidth - itemIndent; + let formattedDescription; + if (remainingWidth < this.minWidthToWrap || helper.preformatted(description)) { + formattedDescription = description; + } else { + const wrappedDescription = helper.boxWrap(description, remainingWidth); + formattedDescription = wrappedDescription.replace( + /\n/g, + "\n" + " ".repeat(termWidth + spacerWidth) + ); + } + return itemIndentStr + paddedTerm + " ".repeat(spacerWidth) + formattedDescription.replace(/\n/g, ` +${itemIndentStr}`); + } + /** + * Wrap a string at whitespace, preserving existing line breaks. + * Wrapping is skipped if the width is less than `minWidthToWrap`. + * + * @param {string} str + * @param {number} width + * @returns {string} + */ + boxWrap(str, width) { + if (width < this.minWidthToWrap) return str; + const rawLines = str.split(/\r\n|\n/); + const chunkPattern = /[\s]*[^\s]+/g; + const wrappedLines = []; + rawLines.forEach((line) => { + const chunks = line.match(chunkPattern); + if (chunks === null) { + wrappedLines.push(""); + return; + } + let sumChunks = [chunks.shift()]; + let sumWidth = this.displayWidth(sumChunks[0]); + chunks.forEach((chunk) => { + const visibleWidth = this.displayWidth(chunk); + if (sumWidth + visibleWidth <= width) { + sumChunks.push(chunk); + sumWidth += visibleWidth; + return; + } + wrappedLines.push(sumChunks.join("")); + const nextChunk = chunk.trimStart(); + sumChunks = [nextChunk]; + sumWidth = this.displayWidth(nextChunk); + }); + wrappedLines.push(sumChunks.join("")); + }); + return wrappedLines.join("\n"); + } + }; + function stripColor(str) { + const sgrPattern = /\x1b\[\d*(;\d*)*m/g; + return str.replace(sgrPattern, ""); + } + exports2.Help = Help2; + exports2.stripColor = stripColor; + } +}); + +// node_modules/commander/lib/option.js +var require_option = __commonJS({ + "node_modules/commander/lib/option.js"(exports2) { + var { InvalidArgumentError: InvalidArgumentError2 } = require_error(); + var Option2 = class { + /** + * Initialize a new `Option` with the given `flags` and `description`. + * + * @param {string} flags + * @param {string} [description] + */ + constructor(flags, description) { + this.flags = flags; + this.description = description || ""; + this.required = flags.includes("<"); + this.optional = flags.includes("["); + this.variadic = /\w\.\.\.[>\]]$/.test(flags); + this.mandatory = false; + const optionFlags = splitOptionFlags(flags); + this.short = optionFlags.shortFlag; + this.long = optionFlags.longFlag; + this.negate = false; + if (this.long) { + this.negate = this.long.startsWith("--no-"); + } + this.defaultValue = void 0; + this.defaultValueDescription = void 0; + this.presetArg = void 0; + this.envVar = void 0; + this.parseArg = void 0; + this.hidden = false; + this.argChoices = void 0; + this.conflictsWith = []; + this.implied = void 0; + this.helpGroupHeading = void 0; + } + /** + * Set the default value, and optionally supply the description to be displayed in the help. + * + * @param {*} value + * @param {string} [description] + * @return {Option} + */ + default(value, description) { + this.defaultValue = value; + this.defaultValueDescription = description; + return this; + } + /** + * Preset to use when option used without option-argument, especially optional but also boolean and negated. + * The custom processing (parseArg) is called. + * + * @example + * new Option('--color').default('GREYSCALE').preset('RGB'); + * new Option('--donate [amount]').preset('20').argParser(parseFloat); + * + * @param {*} arg + * @return {Option} + */ + preset(arg) { + this.presetArg = arg; + return this; + } + /** + * Add option name(s) that conflict with this option. + * An error will be displayed if conflicting options are found during parsing. + * + * @example + * new Option('--rgb').conflicts('cmyk'); + * new Option('--js').conflicts(['ts', 'jsx']); + * + * @param {(string | string[])} names + * @return {Option} + */ + conflicts(names) { + this.conflictsWith = this.conflictsWith.concat(names); + return this; + } + /** + * Specify implied option values for when this option is set and the implied options are not. + * + * The custom processing (parseArg) is not called on the implied values. + * + * @example + * program + * .addOption(new Option('--log', 'write logging information to file')) + * .addOption(new Option('--trace', 'log extra details').implies({ log: 'trace.txt' })); + * + * @param {object} impliedOptionValues + * @return {Option} + */ + implies(impliedOptionValues) { + let newImplied = impliedOptionValues; + if (typeof impliedOptionValues === "string") { + newImplied = { [impliedOptionValues]: true }; + } + this.implied = Object.assign(this.implied || {}, newImplied); + return this; + } + /** + * Set environment variable to check for option value. + * + * An environment variable is only used if when processed the current option value is + * undefined, or the source of the current value is 'default' or 'config' or 'env'. + * + * @param {string} name + * @return {Option} + */ + env(name) { + this.envVar = name; + return this; + } + /** + * Set the custom handler for processing CLI option arguments into option values. + * + * @param {Function} [fn] + * @return {Option} + */ + argParser(fn) { + this.parseArg = fn; + return this; + } + /** + * Whether the option is mandatory and must have a value after parsing. + * + * @param {boolean} [mandatory=true] + * @return {Option} + */ + makeOptionMandatory(mandatory = true) { + this.mandatory = !!mandatory; + return this; + } + /** + * Hide option in help. + * + * @param {boolean} [hide=true] + * @return {Option} + */ + hideHelp(hide = true) { + this.hidden = !!hide; + return this; + } + /** + * @package + */ + _collectValue(value, previous) { + if (previous === this.defaultValue || !Array.isArray(previous)) { + return [value]; + } + previous.push(value); + return previous; + } + /** + * Only allow option value to be one of choices. + * + * @param {string[]} values + * @return {Option} + */ + choices(values) { + this.argChoices = values.slice(); + this.parseArg = (arg, previous) => { + if (!this.argChoices.includes(arg)) { + throw new InvalidArgumentError2( + `Allowed choices are ${this.argChoices.join(", ")}.` + ); + } + if (this.variadic) { + return this._collectValue(arg, previous); + } + return arg; + }; + return this; + } + /** + * Return option name. + * + * @return {string} + */ + name() { + if (this.long) { + return this.long.replace(/^--/, ""); + } + return this.short.replace(/^-/, ""); + } + /** + * Return option name, in a camelcase format that can be used + * as an object attribute key. + * + * @return {string} + */ + attributeName() { + if (this.negate) { + return camelcase(this.name().replace(/^no-/, "")); + } + return camelcase(this.name()); + } + /** + * Set the help group heading. + * + * @param {string} heading + * @return {Option} + */ + helpGroup(heading) { + this.helpGroupHeading = heading; + return this; + } + /** + * Check if `arg` matches the short or long flag. + * + * @param {string} arg + * @return {boolean} + * @package + */ + is(arg) { + return this.short === arg || this.long === arg; + } + /** + * Return whether a boolean option. + * + * Options are one of boolean, negated, required argument, or optional argument. + * + * @return {boolean} + * @package + */ + isBoolean() { + return !this.required && !this.optional && !this.negate; + } + }; + var DualOptions = class { + /** + * @param {Option[]} options + */ + constructor(options) { + this.positiveOptions = /* @__PURE__ */ new Map(); + this.negativeOptions = /* @__PURE__ */ new Map(); + this.dualOptions = /* @__PURE__ */ new Set(); + options.forEach((option) => { + if (option.negate) { + this.negativeOptions.set(option.attributeName(), option); + } else { + this.positiveOptions.set(option.attributeName(), option); + } + }); + this.negativeOptions.forEach((value, key) => { + if (this.positiveOptions.has(key)) { + this.dualOptions.add(key); + } + }); + } + /** + * Did the value come from the option, and not from possible matching dual option? + * + * @param {*} value + * @param {Option} option + * @returns {boolean} + */ + valueFromOption(value, option) { + const optionKey = option.attributeName(); + if (!this.dualOptions.has(optionKey)) return true; + const preset = this.negativeOptions.get(optionKey).presetArg; + const negativeValue = preset !== void 0 ? preset : false; + return option.negate === (negativeValue === value); + } + }; + function camelcase(str) { + return str.split("-").reduce((str2, word) => { + return str2 + word[0].toUpperCase() + word.slice(1); + }); + } + function splitOptionFlags(flags) { + let shortFlag; + let longFlag; + const shortFlagExp = /^-[^-]$/; + const longFlagExp = /^--[^-]/; + const flagParts = flags.split(/[ |,]+/).concat("guard"); + if (shortFlagExp.test(flagParts[0])) shortFlag = flagParts.shift(); + if (longFlagExp.test(flagParts[0])) longFlag = flagParts.shift(); + if (!shortFlag && shortFlagExp.test(flagParts[0])) + shortFlag = flagParts.shift(); + if (!shortFlag && longFlagExp.test(flagParts[0])) { + shortFlag = longFlag; + longFlag = flagParts.shift(); + } + if (flagParts[0].startsWith("-")) { + const unsupportedFlag = flagParts[0]; + const baseError = `option creation failed due to '${unsupportedFlag}' in option flags '${flags}'`; + if (/^-[^-][^-]/.test(unsupportedFlag)) + throw new Error( + `${baseError} +- a short flag is a single dash and a single character + - either use a single dash and a single character (for a short flag) + - or use a double dash for a long option (and can have two, like '--ws, --workspace')` + ); + if (shortFlagExp.test(unsupportedFlag)) + throw new Error(`${baseError} +- too many short flags`); + if (longFlagExp.test(unsupportedFlag)) + throw new Error(`${baseError} +- too many long flags`); + throw new Error(`${baseError} +- unrecognised flag format`); + } + if (shortFlag === void 0 && longFlag === void 0) + throw new Error( + `option creation failed due to no flags found in '${flags}'.` + ); + return { shortFlag, longFlag }; + } + exports2.Option = Option2; + exports2.DualOptions = DualOptions; + } +}); + +// node_modules/commander/lib/suggestSimilar.js +var require_suggestSimilar = __commonJS({ + "node_modules/commander/lib/suggestSimilar.js"(exports2) { + var maxDistance = 3; + function editDistance(a, b) { + if (Math.abs(a.length - b.length) > maxDistance) + return Math.max(a.length, b.length); + const d = []; + for (let i = 0; i <= a.length; i++) { + d[i] = [i]; + } + for (let j = 0; j <= b.length; j++) { + d[0][j] = j; + } + for (let j = 1; j <= b.length; j++) { + for (let i = 1; i <= a.length; i++) { + let cost = 1; + if (a[i - 1] === b[j - 1]) { + cost = 0; + } else { + cost = 1; + } + d[i][j] = Math.min( + d[i - 1][j] + 1, + // deletion + d[i][j - 1] + 1, + // insertion + d[i - 1][j - 1] + cost + // substitution + ); + if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) { + d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + 1); + } + } + } + return d[a.length][b.length]; + } + function suggestSimilar(word, candidates) { + if (!candidates || candidates.length === 0) return ""; + candidates = Array.from(new Set(candidates)); + const searchingOptions = word.startsWith("--"); + if (searchingOptions) { + word = word.slice(2); + candidates = candidates.map((candidate) => candidate.slice(2)); + } + let similar = []; + let bestDistance = maxDistance; + const minSimilarity = 0.4; + candidates.forEach((candidate) => { + if (candidate.length <= 1) return; + const distance = editDistance(word, candidate); + const length = Math.max(word.length, candidate.length); + const similarity = (length - distance) / length; + if (similarity > minSimilarity) { + if (distance < bestDistance) { + bestDistance = distance; + similar = [candidate]; + } else if (distance === bestDistance) { + similar.push(candidate); + } + } + }); + similar.sort((a, b) => a.localeCompare(b)); + if (searchingOptions) { + similar = similar.map((candidate) => `--${candidate}`); + } + if (similar.length > 1) { + return ` +(Did you mean one of ${similar.join(", ")}?)`; + } + if (similar.length === 1) { + return ` +(Did you mean ${similar[0]}?)`; + } + return ""; + } + exports2.suggestSimilar = suggestSimilar; + } +}); + +// node_modules/commander/lib/command.js +var require_command = __commonJS({ + "node_modules/commander/lib/command.js"(exports2) { + var EventEmitter = require("node:events").EventEmitter; + var childProcess = require("node:child_process"); + var path20 = require("node:path"); + var fs14 = require("node:fs"); + var process2 = require("node:process"); + var { Argument: Argument2, humanReadableArgName } = require_argument(); + var { CommanderError: CommanderError2 } = require_error(); + var { Help: Help2, stripColor } = require_help(); + var { Option: Option2, DualOptions } = require_option(); + var { suggestSimilar } = require_suggestSimilar(); + var Command2 = class _Command extends EventEmitter { + /** + * Initialize a new `Command`. + * + * @param {string} [name] + */ + constructor(name) { + super(); + this.commands = []; + this.options = []; + this.parent = null; + this._allowUnknownOption = false; + this._allowExcessArguments = false; + this.registeredArguments = []; + this._args = this.registeredArguments; + this.args = []; + this.rawArgs = []; + this.processedArgs = []; + this._scriptPath = null; + this._name = name || ""; + this._optionValues = {}; + this._optionValueSources = {}; + this._storeOptionsAsProperties = false; + this._actionHandler = null; + this._executableHandler = false; + this._executableFile = null; + this._executableDir = null; + this._defaultCommandName = null; + this._exitCallback = null; + this._aliases = []; + this._combineFlagAndOptionalValue = true; + this._description = ""; + this._summary = ""; + this._argsDescription = void 0; + this._enablePositionalOptions = false; + this._passThroughOptions = false; + this._lifeCycleHooks = {}; + this._showHelpAfterError = false; + this._showSuggestionAfterError = true; + this._savedState = null; + this._outputConfiguration = { + writeOut: (str) => process2.stdout.write(str), + writeErr: (str) => process2.stderr.write(str), + outputError: (str, write) => write(str), + getOutHelpWidth: () => process2.stdout.isTTY ? process2.stdout.columns : void 0, + getErrHelpWidth: () => process2.stderr.isTTY ? process2.stderr.columns : void 0, + getOutHasColors: () => useColor() ?? (process2.stdout.isTTY && process2.stdout.hasColors?.()), + getErrHasColors: () => useColor() ?? (process2.stderr.isTTY && process2.stderr.hasColors?.()), + stripColor: (str) => stripColor(str) + }; + this._hidden = false; + this._helpOption = void 0; + this._addImplicitHelpCommand = void 0; + this._helpCommand = void 0; + this._helpConfiguration = {}; + this._helpGroupHeading = void 0; + this._defaultCommandGroup = void 0; + this._defaultOptionGroup = void 0; + } + /** + * Copy settings that are useful to have in common across root command and subcommands. + * + * (Used internally when adding a command using `.command()` so subcommands inherit parent settings.) + * + * @param {Command} sourceCommand + * @return {Command} `this` command for chaining + */ + copyInheritedSettings(sourceCommand) { + this._outputConfiguration = sourceCommand._outputConfiguration; + this._helpOption = sourceCommand._helpOption; + this._helpCommand = sourceCommand._helpCommand; + this._helpConfiguration = sourceCommand._helpConfiguration; + this._exitCallback = sourceCommand._exitCallback; + this._storeOptionsAsProperties = sourceCommand._storeOptionsAsProperties; + this._combineFlagAndOptionalValue = sourceCommand._combineFlagAndOptionalValue; + this._allowExcessArguments = sourceCommand._allowExcessArguments; + this._enablePositionalOptions = sourceCommand._enablePositionalOptions; + this._showHelpAfterError = sourceCommand._showHelpAfterError; + this._showSuggestionAfterError = sourceCommand._showSuggestionAfterError; + return this; + } + /** + * @returns {Command[]} + * @private + */ + _getCommandAndAncestors() { + const result = []; + for (let command = this; command; command = command.parent) { + result.push(command); + } + return result; + } + /** + * Define a command. + * + * There are two styles of command: pay attention to where to put the description. + * + * @example + * // Command implemented using action handler (description is supplied separately to `.command`) + * program + * .command('clone [destination]') + * .description('clone a repository into a newly created directory') + * .action((source, destination) => { + * console.log('clone command called'); + * }); + * + * // Command implemented using separate executable file (description is second parameter to `.command`) + * program + * .command('start ', 'start named service') + * .command('stop [service]', 'stop named service, or all if no name supplied'); + * + * @param {string} nameAndArgs - command name and arguments, args are `` or `[optional]` and last may also be `variadic...` + * @param {(object | string)} [actionOptsOrExecDesc] - configuration options (for action), or description (for executable) + * @param {object} [execOpts] - configuration options (for executable) + * @return {Command} returns new command for action handler, or `this` for executable command + */ + command(nameAndArgs, actionOptsOrExecDesc, execOpts) { + let desc = actionOptsOrExecDesc; + let opts = execOpts; + if (typeof desc === "object" && desc !== null) { + opts = desc; + desc = null; + } + opts = opts || {}; + const [, name, args] = nameAndArgs.match(/([^ ]+) *(.*)/); + const cmd = this.createCommand(name); + if (desc) { + cmd.description(desc); + cmd._executableHandler = true; + } + if (opts.isDefault) this._defaultCommandName = cmd._name; + cmd._hidden = !!(opts.noHelp || opts.hidden); + cmd._executableFile = opts.executableFile || null; + if (args) cmd.arguments(args); + this._registerCommand(cmd); + cmd.parent = this; + cmd.copyInheritedSettings(this); + if (desc) return this; + return cmd; + } + /** + * Factory routine to create a new unattached command. + * + * See .command() for creating an attached subcommand, which uses this routine to + * create the command. You can override createCommand to customise subcommands. + * + * @param {string} [name] + * @return {Command} new command + */ + createCommand(name) { + return new _Command(name); + } + /** + * You can customise the help with a subclass of Help by overriding createHelp, + * or by overriding Help properties using configureHelp(). + * + * @return {Help} + */ + createHelp() { + return Object.assign(new Help2(), this.configureHelp()); + } + /** + * You can customise the help by overriding Help properties using configureHelp(), + * or with a subclass of Help by overriding createHelp(). + * + * @param {object} [configuration] - configuration options + * @return {(Command | object)} `this` command for chaining, or stored configuration + */ + configureHelp(configuration) { + if (configuration === void 0) return this._helpConfiguration; + this._helpConfiguration = configuration; + return this; + } + /** + * The default output goes to stdout and stderr. You can customise this for special + * applications. You can also customise the display of errors by overriding outputError. + * + * The configuration properties are all functions: + * + * // change how output being written, defaults to stdout and stderr + * writeOut(str) + * writeErr(str) + * // change how output being written for errors, defaults to writeErr + * outputError(str, write) // used for displaying errors and not used for displaying help + * // specify width for wrapping help + * getOutHelpWidth() + * getErrHelpWidth() + * // color support, currently only used with Help + * getOutHasColors() + * getErrHasColors() + * stripColor() // used to remove ANSI escape codes if output does not have colors + * + * @param {object} [configuration] - configuration options + * @return {(Command | object)} `this` command for chaining, or stored configuration + */ + configureOutput(configuration) { + if (configuration === void 0) return this._outputConfiguration; + this._outputConfiguration = { + ...this._outputConfiguration, + ...configuration + }; + return this; + } + /** + * Display the help or a custom message after an error occurs. + * + * @param {(boolean|string)} [displayHelp] + * @return {Command} `this` command for chaining + */ + showHelpAfterError(displayHelp = true) { + if (typeof displayHelp !== "string") displayHelp = !!displayHelp; + this._showHelpAfterError = displayHelp; + return this; + } + /** + * Display suggestion of similar commands for unknown commands, or options for unknown options. + * + * @param {boolean} [displaySuggestion] + * @return {Command} `this` command for chaining + */ + showSuggestionAfterError(displaySuggestion = true) { + this._showSuggestionAfterError = !!displaySuggestion; + return this; + } + /** + * Add a prepared subcommand. + * + * See .command() for creating an attached subcommand which inherits settings from its parent. + * + * @param {Command} cmd - new subcommand + * @param {object} [opts] - configuration options + * @return {Command} `this` command for chaining + */ + addCommand(cmd, opts) { + if (!cmd._name) { + throw new Error(`Command passed to .addCommand() must have a name +- specify the name in Command constructor or using .name()`); + } + opts = opts || {}; + if (opts.isDefault) this._defaultCommandName = cmd._name; + if (opts.noHelp || opts.hidden) cmd._hidden = true; + this._registerCommand(cmd); + cmd.parent = this; + cmd._checkForBrokenPassThrough(); + return this; + } + /** + * Factory routine to create a new unattached argument. + * + * See .argument() for creating an attached argument, which uses this routine to + * create the argument. You can override createArgument to return a custom argument. + * + * @param {string} name + * @param {string} [description] + * @return {Argument} new argument + */ + createArgument(name, description) { + return new Argument2(name, description); + } + /** + * Define argument syntax for command. + * + * The default is that the argument is required, and you can explicitly + * indicate this with <> around the name. Put [] around the name for an optional argument. + * + * @example + * program.argument(''); + * program.argument('[output-file]'); + * + * @param {string} name + * @param {string} [description] + * @param {(Function|*)} [parseArg] - custom argument processing function or default value + * @param {*} [defaultValue] + * @return {Command} `this` command for chaining + */ + argument(name, description, parseArg, defaultValue) { + const argument = this.createArgument(name, description); + if (typeof parseArg === "function") { + argument.default(defaultValue).argParser(parseArg); + } else { + argument.default(parseArg); + } + this.addArgument(argument); + return this; + } + /** + * Define argument syntax for command, adding multiple at once (without descriptions). + * + * See also .argument(). + * + * @example + * program.arguments(' [env]'); + * + * @param {string} names + * @return {Command} `this` command for chaining + */ + arguments(names) { + names.trim().split(/ +/).forEach((detail) => { + this.argument(detail); + }); + return this; + } + /** + * Define argument syntax for command, adding a prepared argument. + * + * @param {Argument} argument + * @return {Command} `this` command for chaining + */ + addArgument(argument) { + const previousArgument = this.registeredArguments.slice(-1)[0]; + if (previousArgument?.variadic) { + throw new Error( + `only the last argument can be variadic '${previousArgument.name()}'` + ); + } + if (argument.required && argument.defaultValue !== void 0 && argument.parseArg === void 0) { + throw new Error( + `a default value for a required argument is never used: '${argument.name()}'` + ); + } + this.registeredArguments.push(argument); + return this; + } + /** + * Customise or override default help command. By default a help command is automatically added if your command has subcommands. + * + * @example + * program.helpCommand('help [cmd]'); + * program.helpCommand('help [cmd]', 'show help'); + * program.helpCommand(false); // suppress default help command + * program.helpCommand(true); // add help command even if no subcommands + * + * @param {string|boolean} enableOrNameAndArgs - enable with custom name and/or arguments, or boolean to override whether added + * @param {string} [description] - custom description + * @return {Command} `this` command for chaining + */ + helpCommand(enableOrNameAndArgs, description) { + if (typeof enableOrNameAndArgs === "boolean") { + this._addImplicitHelpCommand = enableOrNameAndArgs; + if (enableOrNameAndArgs && this._defaultCommandGroup) { + this._initCommandGroup(this._getHelpCommand()); + } + return this; + } + const nameAndArgs = enableOrNameAndArgs ?? "help [command]"; + const [, helpName, helpArgs] = nameAndArgs.match(/([^ ]+) *(.*)/); + const helpDescription = description ?? "display help for command"; + const helpCommand = this.createCommand(helpName); + helpCommand.helpOption(false); + if (helpArgs) helpCommand.arguments(helpArgs); + if (helpDescription) helpCommand.description(helpDescription); + this._addImplicitHelpCommand = true; + this._helpCommand = helpCommand; + if (enableOrNameAndArgs || description) this._initCommandGroup(helpCommand); + return this; + } + /** + * Add prepared custom help command. + * + * @param {(Command|string|boolean)} helpCommand - custom help command, or deprecated enableOrNameAndArgs as for `.helpCommand()` + * @param {string} [deprecatedDescription] - deprecated custom description used with custom name only + * @return {Command} `this` command for chaining + */ + addHelpCommand(helpCommand, deprecatedDescription) { + if (typeof helpCommand !== "object") { + this.helpCommand(helpCommand, deprecatedDescription); + return this; + } + this._addImplicitHelpCommand = true; + this._helpCommand = helpCommand; + this._initCommandGroup(helpCommand); + return this; + } + /** + * Lazy create help command. + * + * @return {(Command|null)} + * @package + */ + _getHelpCommand() { + const hasImplicitHelpCommand = this._addImplicitHelpCommand ?? (this.commands.length && !this._actionHandler && !this._findCommand("help")); + if (hasImplicitHelpCommand) { + if (this._helpCommand === void 0) { + this.helpCommand(void 0, void 0); + } + return this._helpCommand; + } + return null; + } + /** + * Add hook for life cycle event. + * + * @param {string} event + * @param {Function} listener + * @return {Command} `this` command for chaining + */ + hook(event, listener) { + const allowedValues = ["preSubcommand", "preAction", "postAction"]; + if (!allowedValues.includes(event)) { + throw new Error(`Unexpected value for event passed to hook : '${event}'. +Expecting one of '${allowedValues.join("', '")}'`); + } + if (this._lifeCycleHooks[event]) { + this._lifeCycleHooks[event].push(listener); + } else { + this._lifeCycleHooks[event] = [listener]; + } + return this; + } + /** + * Register callback to use as replacement for calling process.exit. + * + * @param {Function} [fn] optional callback which will be passed a CommanderError, defaults to throwing + * @return {Command} `this` command for chaining + */ + exitOverride(fn) { + if (fn) { + this._exitCallback = fn; + } else { + this._exitCallback = (err) => { + if (err.code !== "commander.executeSubCommandAsync") { + throw err; + } else { + } + }; + } + return this; + } + /** + * Call process.exit, and _exitCallback if defined. + * + * @param {number} exitCode exit code for using with process.exit + * @param {string} code an id string representing the error + * @param {string} message human-readable description of the error + * @return never + * @private + */ + _exit(exitCode, code, message) { + if (this._exitCallback) { + this._exitCallback(new CommanderError2(exitCode, code, message)); + } + process2.exit(exitCode); + } + /** + * Register callback `fn` for the command. + * + * @example + * program + * .command('serve') + * .description('start service') + * .action(function() { + * // do work here + * }); + * + * @param {Function} fn + * @return {Command} `this` command for chaining + */ + action(fn) { + const listener = (args) => { + const expectedArgsCount = this.registeredArguments.length; + const actionArgs = args.slice(0, expectedArgsCount); + if (this._storeOptionsAsProperties) { + actionArgs[expectedArgsCount] = this; + } else { + actionArgs[expectedArgsCount] = this.opts(); + } + actionArgs.push(this); + return fn.apply(this, actionArgs); + }; + this._actionHandler = listener; + return this; + } + /** + * Factory routine to create a new unattached option. + * + * See .option() for creating an attached option, which uses this routine to + * create the option. You can override createOption to return a custom option. + * + * @param {string} flags + * @param {string} [description] + * @return {Option} new option + */ + createOption(flags, description) { + return new Option2(flags, description); + } + /** + * Wrap parseArgs to catch 'commander.invalidArgument'. + * + * @param {(Option | Argument)} target + * @param {string} value + * @param {*} previous + * @param {string} invalidArgumentMessage + * @private + */ + _callParseArg(target, value, previous, invalidArgumentMessage) { + try { + return target.parseArg(value, previous); + } catch (err) { + if (err.code === "commander.invalidArgument") { + const message = `${invalidArgumentMessage} ${err.message}`; + this.error(message, { exitCode: err.exitCode, code: err.code }); + } + throw err; + } + } + /** + * Check for option flag conflicts. + * Register option if no conflicts found, or throw on conflict. + * + * @param {Option} option + * @private + */ + _registerOption(option) { + const matchingOption = option.short && this._findOption(option.short) || option.long && this._findOption(option.long); + if (matchingOption) { + const matchingFlag = option.long && this._findOption(option.long) ? option.long : option.short; + throw new Error(`Cannot add option '${option.flags}'${this._name && ` to command '${this._name}'`} due to conflicting flag '${matchingFlag}' +- already used by option '${matchingOption.flags}'`); + } + this._initOptionGroup(option); + this.options.push(option); + } + /** + * Check for command name and alias conflicts with existing commands. + * Register command if no conflicts found, or throw on conflict. + * + * @param {Command} command + * @private + */ + _registerCommand(command) { + const knownBy = (cmd) => { + return [cmd.name()].concat(cmd.aliases()); + }; + const alreadyUsed = knownBy(command).find( + (name) => this._findCommand(name) + ); + if (alreadyUsed) { + const existingCmd = knownBy(this._findCommand(alreadyUsed)).join("|"); + const newCmd = knownBy(command).join("|"); + throw new Error( + `cannot add command '${newCmd}' as already have command '${existingCmd}'` + ); + } + this._initCommandGroup(command); + this.commands.push(command); + } + /** + * Add an option. + * + * @param {Option} option + * @return {Command} `this` command for chaining + */ + addOption(option) { + this._registerOption(option); + const oname = option.name(); + const name = option.attributeName(); + if (option.negate) { + const positiveLongFlag = option.long.replace(/^--no-/, "--"); + if (!this._findOption(positiveLongFlag)) { + this.setOptionValueWithSource( + name, + option.defaultValue === void 0 ? true : option.defaultValue, + "default" + ); + } + } else if (option.defaultValue !== void 0) { + this.setOptionValueWithSource(name, option.defaultValue, "default"); + } + const handleOptionValue = (val, invalidValueMessage, valueSource) => { + if (val == null && option.presetArg !== void 0) { + val = option.presetArg; + } + const oldValue = this.getOptionValue(name); + if (val !== null && option.parseArg) { + val = this._callParseArg(option, val, oldValue, invalidValueMessage); + } else if (val !== null && option.variadic) { + val = option._collectValue(val, oldValue); + } + if (val == null) { + if (option.negate) { + val = false; + } else if (option.isBoolean() || option.optional) { + val = true; + } else { + val = ""; + } + } + this.setOptionValueWithSource(name, val, valueSource); + }; + this.on("option:" + oname, (val) => { + const invalidValueMessage = `error: option '${option.flags}' argument '${val}' is invalid.`; + handleOptionValue(val, invalidValueMessage, "cli"); + }); + if (option.envVar) { + this.on("optionEnv:" + oname, (val) => { + const invalidValueMessage = `error: option '${option.flags}' value '${val}' from env '${option.envVar}' is invalid.`; + handleOptionValue(val, invalidValueMessage, "env"); + }); + } + return this; + } + /** + * Internal implementation shared by .option() and .requiredOption() + * + * @return {Command} `this` command for chaining + * @private + */ + _optionEx(config, flags, description, fn, defaultValue) { + if (typeof flags === "object" && flags instanceof Option2) { + throw new Error( + "To add an Option object use addOption() instead of option() or requiredOption()" + ); + } + const option = this.createOption(flags, description); + option.makeOptionMandatory(!!config.mandatory); + if (typeof fn === "function") { + option.default(defaultValue).argParser(fn); + } else if (fn instanceof RegExp) { + const regex = fn; + fn = (val, def) => { + const m = regex.exec(val); + return m ? m[0] : def; + }; + option.default(defaultValue).argParser(fn); + } else { + option.default(fn); + } + return this.addOption(option); + } + /** + * Define option with `flags`, `description`, and optional argument parsing function or `defaultValue` or both. + * + * The `flags` string contains the short and/or long flags, separated by comma, a pipe or space. A required + * option-argument is indicated by `<>` and an optional option-argument by `[]`. + * + * See the README for more details, and see also addOption() and requiredOption(). + * + * @example + * program + * .option('-p, --pepper', 'add pepper') + * .option('--pt, --pizza-type ', 'type of pizza') // required option-argument + * .option('-c, --cheese [CHEESE]', 'add extra cheese', 'mozzarella') // optional option-argument with default + * .option('-t, --tip ', 'add tip to purchase cost', parseFloat) // custom parse function + * + * @param {string} flags + * @param {string} [description] + * @param {(Function|*)} [parseArg] - custom option processing function or default value + * @param {*} [defaultValue] + * @return {Command} `this` command for chaining + */ + option(flags, description, parseArg, defaultValue) { + return this._optionEx({}, flags, description, parseArg, defaultValue); + } + /** + * Add a required option which must have a value after parsing. This usually means + * the option must be specified on the command line. (Otherwise the same as .option().) + * + * The `flags` string contains the short and/or long flags, separated by comma, a pipe or space. + * + * @param {string} flags + * @param {string} [description] + * @param {(Function|*)} [parseArg] - custom option processing function or default value + * @param {*} [defaultValue] + * @return {Command} `this` command for chaining + */ + requiredOption(flags, description, parseArg, defaultValue) { + return this._optionEx( + { mandatory: true }, + flags, + description, + parseArg, + defaultValue + ); + } + /** + * Alter parsing of short flags with optional values. + * + * @example + * // for `.option('-f,--flag [value]'): + * program.combineFlagAndOptionalValue(true); // `-f80` is treated like `--flag=80`, this is the default behaviour + * program.combineFlagAndOptionalValue(false) // `-fb` is treated like `-f -b` + * + * @param {boolean} [combine] - if `true` or omitted, an optional value can be specified directly after the flag. + * @return {Command} `this` command for chaining + */ + combineFlagAndOptionalValue(combine = true) { + this._combineFlagAndOptionalValue = !!combine; + return this; + } + /** + * Allow unknown options on the command line. + * + * @param {boolean} [allowUnknown] - if `true` or omitted, no error will be thrown for unknown options. + * @return {Command} `this` command for chaining + */ + allowUnknownOption(allowUnknown = true) { + this._allowUnknownOption = !!allowUnknown; + return this; + } + /** + * Allow excess command-arguments on the command line. Pass false to make excess arguments an error. + * + * @param {boolean} [allowExcess] - if `true` or omitted, no error will be thrown for excess arguments. + * @return {Command} `this` command for chaining + */ + allowExcessArguments(allowExcess = true) { + this._allowExcessArguments = !!allowExcess; + return this; + } + /** + * Enable positional options. Positional means global options are specified before subcommands which lets + * subcommands reuse the same option names, and also enables subcommands to turn on passThroughOptions. + * The default behaviour is non-positional and global options may appear anywhere on the command line. + * + * @param {boolean} [positional] + * @return {Command} `this` command for chaining + */ + enablePositionalOptions(positional = true) { + this._enablePositionalOptions = !!positional; + return this; + } + /** + * Pass through options that come after command-arguments rather than treat them as command-options, + * so actual command-options come before command-arguments. Turning this on for a subcommand requires + * positional options to have been enabled on the program (parent commands). + * The default behaviour is non-positional and options may appear before or after command-arguments. + * + * @param {boolean} [passThrough] for unknown options. + * @return {Command} `this` command for chaining + */ + passThroughOptions(passThrough = true) { + this._passThroughOptions = !!passThrough; + this._checkForBrokenPassThrough(); + return this; + } + /** + * @private + */ + _checkForBrokenPassThrough() { + if (this.parent && this._passThroughOptions && !this.parent._enablePositionalOptions) { + throw new Error( + `passThroughOptions cannot be used for '${this._name}' without turning on enablePositionalOptions for parent command(s)` + ); + } + } + /** + * Whether to store option values as properties on command object, + * or store separately (specify false). In both cases the option values can be accessed using .opts(). + * + * @param {boolean} [storeAsProperties=true] + * @return {Command} `this` command for chaining + */ + storeOptionsAsProperties(storeAsProperties = true) { + if (this.options.length) { + throw new Error("call .storeOptionsAsProperties() before adding options"); + } + if (Object.keys(this._optionValues).length) { + throw new Error( + "call .storeOptionsAsProperties() before setting option values" + ); + } + this._storeOptionsAsProperties = !!storeAsProperties; + return this; + } + /** + * Retrieve option value. + * + * @param {string} key + * @return {object} value + */ + getOptionValue(key) { + if (this._storeOptionsAsProperties) { + return this[key]; + } + return this._optionValues[key]; + } + /** + * Store option value. + * + * @param {string} key + * @param {object} value + * @return {Command} `this` command for chaining + */ + setOptionValue(key, value) { + return this.setOptionValueWithSource(key, value, void 0); + } + /** + * Store option value and where the value came from. + * + * @param {string} key + * @param {object} value + * @param {string} source - expected values are default/config/env/cli/implied + * @return {Command} `this` command for chaining + */ + setOptionValueWithSource(key, value, source) { + if (this._storeOptionsAsProperties) { + this[key] = value; + } else { + this._optionValues[key] = value; + } + this._optionValueSources[key] = source; + return this; + } + /** + * Get source of option value. + * Expected values are default | config | env | cli | implied + * + * @param {string} key + * @return {string} + */ + getOptionValueSource(key) { + return this._optionValueSources[key]; + } + /** + * Get source of option value. See also .optsWithGlobals(). + * Expected values are default | config | env | cli | implied + * + * @param {string} key + * @return {string} + */ + getOptionValueSourceWithGlobals(key) { + let source; + this._getCommandAndAncestors().forEach((cmd) => { + if (cmd.getOptionValueSource(key) !== void 0) { + source = cmd.getOptionValueSource(key); + } + }); + return source; + } + /** + * Get user arguments from implied or explicit arguments. + * Side-effects: set _scriptPath if args included script. Used for default program name, and subcommand searches. + * + * @private + */ + _prepareUserArgs(argv, parseOptions) { + if (argv !== void 0 && !Array.isArray(argv)) { + throw new Error("first parameter to parse must be array or undefined"); + } + parseOptions = parseOptions || {}; + if (argv === void 0 && parseOptions.from === void 0) { + if (process2.versions?.electron) { + parseOptions.from = "electron"; + } + const execArgv = process2.execArgv ?? []; + if (execArgv.includes("-e") || execArgv.includes("--eval") || execArgv.includes("-p") || execArgv.includes("--print")) { + parseOptions.from = "eval"; + } + } + if (argv === void 0) { + argv = process2.argv; + } + this.rawArgs = argv.slice(); + let userArgs; + switch (parseOptions.from) { + case void 0: + case "node": + this._scriptPath = argv[1]; + userArgs = argv.slice(2); + break; + case "electron": + if (process2.defaultApp) { + this._scriptPath = argv[1]; + userArgs = argv.slice(2); + } else { + userArgs = argv.slice(1); + } + break; + case "user": + userArgs = argv.slice(0); + break; + case "eval": + userArgs = argv.slice(1); + break; + default: + throw new Error( + `unexpected parse option { from: '${parseOptions.from}' }` + ); + } + if (!this._name && this._scriptPath) + this.nameFromFilename(this._scriptPath); + this._name = this._name || "program"; + return userArgs; + } + /** + * Parse `argv`, setting options and invoking commands when defined. + * + * Use parseAsync instead of parse if any of your action handlers are async. + * + * Call with no parameters to parse `process.argv`. Detects Electron and special node options like `node --eval`. Easy mode! + * + * Or call with an array of strings to parse, and optionally where the user arguments start by specifying where the arguments are `from`: + * - `'node'`: default, `argv[0]` is the application and `argv[1]` is the script being run, with user arguments after that + * - `'electron'`: `argv[0]` is the application and `argv[1]` varies depending on whether the electron application is packaged + * - `'user'`: just user arguments + * + * @example + * program.parse(); // parse process.argv and auto-detect electron and special node flags + * program.parse(process.argv); // assume argv[0] is app and argv[1] is script + * program.parse(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0] + * + * @param {string[]} [argv] - optional, defaults to process.argv + * @param {object} [parseOptions] - optionally specify style of options with from: node/user/electron + * @param {string} [parseOptions.from] - where the args are from: 'node', 'user', 'electron' + * @return {Command} `this` command for chaining + */ + parse(argv, parseOptions) { + this._prepareForParse(); + const userArgs = this._prepareUserArgs(argv, parseOptions); + this._parseCommand([], userArgs); + return this; + } + /** + * Parse `argv`, setting options and invoking commands when defined. + * + * Call with no parameters to parse `process.argv`. Detects Electron and special node options like `node --eval`. Easy mode! + * + * Or call with an array of strings to parse, and optionally where the user arguments start by specifying where the arguments are `from`: + * - `'node'`: default, `argv[0]` is the application and `argv[1]` is the script being run, with user arguments after that + * - `'electron'`: `argv[0]` is the application and `argv[1]` varies depending on whether the electron application is packaged + * - `'user'`: just user arguments + * + * @example + * await program.parseAsync(); // parse process.argv and auto-detect electron and special node flags + * await program.parseAsync(process.argv); // assume argv[0] is app and argv[1] is script + * await program.parseAsync(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0] + * + * @param {string[]} [argv] + * @param {object} [parseOptions] + * @param {string} parseOptions.from - where the args are from: 'node', 'user', 'electron' + * @return {Promise} + */ + async parseAsync(argv, parseOptions) { + this._prepareForParse(); + const userArgs = this._prepareUserArgs(argv, parseOptions); + await this._parseCommand([], userArgs); + return this; + } + _prepareForParse() { + if (this._savedState === null) { + this.saveStateBeforeParse(); + } else { + this.restoreStateBeforeParse(); + } + } + /** + * Called the first time parse is called to save state and allow a restore before subsequent calls to parse. + * Not usually called directly, but available for subclasses to save their custom state. + * + * This is called in a lazy way. Only commands used in parsing chain will have state saved. + */ + saveStateBeforeParse() { + this._savedState = { + // name is stable if supplied by author, but may be unspecified for root command and deduced during parsing + _name: this._name, + // option values before parse have default values (including false for negated options) + // shallow clones + _optionValues: { ...this._optionValues }, + _optionValueSources: { ...this._optionValueSources } + }; + } + /** + * Restore state before parse for calls after the first. + * Not usually called directly, but available for subclasses to save their custom state. + * + * This is called in a lazy way. Only commands used in parsing chain will have state restored. + */ + restoreStateBeforeParse() { + if (this._storeOptionsAsProperties) + throw new Error(`Can not call parse again when storeOptionsAsProperties is true. +- either make a new Command for each call to parse, or stop storing options as properties`); + this._name = this._savedState._name; + this._scriptPath = null; + this.rawArgs = []; + this._optionValues = { ...this._savedState._optionValues }; + this._optionValueSources = { ...this._savedState._optionValueSources }; + this.args = []; + this.processedArgs = []; + } + /** + * Throw if expected executable is missing. Add lots of help for author. + * + * @param {string} executableFile + * @param {string} executableDir + * @param {string} subcommandName + */ + _checkForMissingExecutable(executableFile, executableDir, subcommandName) { + if (fs14.existsSync(executableFile)) return; + const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory"; + const executableMissing = `'${executableFile}' does not exist + - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead + - if the default executable name is not suitable, use the executableFile option to supply a custom name or path + - ${executableDirMessage}`; + throw new Error(executableMissing); + } + /** + * Execute a sub-command executable. + * + * @private + */ + _executeSubCommand(subcommand, args) { + args = args.slice(); + let launchWithNode = false; + const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"]; + function findFile(baseDir, baseName) { + const localBin = path20.resolve(baseDir, baseName); + if (fs14.existsSync(localBin)) return localBin; + if (sourceExt.includes(path20.extname(baseName))) return void 0; + const foundExt = sourceExt.find( + (ext) => fs14.existsSync(`${localBin}${ext}`) + ); + if (foundExt) return `${localBin}${foundExt}`; + return void 0; + } + this._checkForMissingMandatoryOptions(); + this._checkForConflictingOptions(); + let executableFile = subcommand._executableFile || `${this._name}-${subcommand._name}`; + let executableDir = this._executableDir || ""; + if (this._scriptPath) { + let resolvedScriptPath; + try { + resolvedScriptPath = fs14.realpathSync(this._scriptPath); + } catch { + resolvedScriptPath = this._scriptPath; + } + executableDir = path20.resolve( + path20.dirname(resolvedScriptPath), + executableDir + ); + } + if (executableDir) { + let localFile = findFile(executableDir, executableFile); + if (!localFile && !subcommand._executableFile && this._scriptPath) { + const legacyName = path20.basename( + this._scriptPath, + path20.extname(this._scriptPath) + ); + if (legacyName !== this._name) { + localFile = findFile( + executableDir, + `${legacyName}-${subcommand._name}` + ); + } + } + executableFile = localFile || executableFile; + } + launchWithNode = sourceExt.includes(path20.extname(executableFile)); + let proc; + if (process2.platform !== "win32") { + if (launchWithNode) { + args.unshift(executableFile); + args = incrementNodeInspectorPort(process2.execArgv).concat(args); + proc = childProcess.spawn(process2.argv[0], args, { stdio: "inherit" }); + } else { + proc = childProcess.spawn(executableFile, args, { stdio: "inherit" }); + } + } else { + this._checkForMissingExecutable( + executableFile, + executableDir, + subcommand._name + ); + args.unshift(executableFile); + args = incrementNodeInspectorPort(process2.execArgv).concat(args); + proc = childProcess.spawn(process2.execPath, args, { stdio: "inherit" }); + } + if (!proc.killed) { + const signals = ["SIGUSR1", "SIGUSR2", "SIGTERM", "SIGINT", "SIGHUP"]; + signals.forEach((signal) => { + process2.on(signal, () => { + if (proc.killed === false && proc.exitCode === null) { + proc.kill(signal); + } + }); + }); + } + const exitCallback = this._exitCallback; + proc.on("close", (code) => { + code = code ?? 1; + if (!exitCallback) { + process2.exit(code); + } else { + exitCallback( + new CommanderError2( + code, + "commander.executeSubCommandAsync", + "(close)" + ) + ); + } + }); + proc.on("error", (err) => { + if (err.code === "ENOENT") { + this._checkForMissingExecutable( + executableFile, + executableDir, + subcommand._name + ); + } else if (err.code === "EACCES") { + throw new Error(`'${executableFile}' not executable`); + } + if (!exitCallback) { + process2.exit(1); + } else { + const wrappedError = new CommanderError2( + 1, + "commander.executeSubCommandAsync", + "(error)" + ); + wrappedError.nestedError = err; + exitCallback(wrappedError); + } + }); + this.runningCommand = proc; + } + /** + * @private + */ + _dispatchSubcommand(commandName, operands, unknown) { + const subCommand = this._findCommand(commandName); + if (!subCommand) this.help({ error: true }); + subCommand._prepareForParse(); + let promiseChain; + promiseChain = this._chainOrCallSubCommandHook( + promiseChain, + subCommand, + "preSubcommand" + ); + promiseChain = this._chainOrCall(promiseChain, () => { + if (subCommand._executableHandler) { + this._executeSubCommand(subCommand, operands.concat(unknown)); + } else { + return subCommand._parseCommand(operands, unknown); + } + }); + return promiseChain; + } + /** + * Invoke help directly if possible, or dispatch if necessary. + * e.g. help foo + * + * @private + */ + _dispatchHelpCommand(subcommandName) { + if (!subcommandName) { + this.help(); + } + const subCommand = this._findCommand(subcommandName); + if (subCommand && !subCommand._executableHandler) { + subCommand.help(); + } + return this._dispatchSubcommand( + subcommandName, + [], + [this._getHelpOption()?.long ?? this._getHelpOption()?.short ?? "--help"] + ); + } + /** + * Check this.args against expected this.registeredArguments. + * + * @private + */ + _checkNumberOfArguments() { + this.registeredArguments.forEach((arg, i) => { + if (arg.required && this.args[i] == null) { + this.missingArgument(arg.name()); + } + }); + if (this.registeredArguments.length > 0 && this.registeredArguments[this.registeredArguments.length - 1].variadic) { + return; + } + if (this.args.length > this.registeredArguments.length) { + this._excessArguments(this.args); + } + } + /** + * Process this.args using this.registeredArguments and save as this.processedArgs! + * + * @private + */ + _processArguments() { + const myParseArg = (argument, value, previous) => { + let parsedValue = value; + if (value !== null && argument.parseArg) { + const invalidValueMessage = `error: command-argument value '${value}' is invalid for argument '${argument.name()}'.`; + parsedValue = this._callParseArg( + argument, + value, + previous, + invalidValueMessage + ); + } + return parsedValue; + }; + this._checkNumberOfArguments(); + const processedArgs = []; + this.registeredArguments.forEach((declaredArg, index) => { + let value = declaredArg.defaultValue; + if (declaredArg.variadic) { + if (index < this.args.length) { + value = this.args.slice(index); + if (declaredArg.parseArg) { + value = value.reduce((processed, v) => { + return myParseArg(declaredArg, v, processed); + }, declaredArg.defaultValue); + } + } else if (value === void 0) { + value = []; + } + } else if (index < this.args.length) { + value = this.args[index]; + if (declaredArg.parseArg) { + value = myParseArg(declaredArg, value, declaredArg.defaultValue); + } + } + processedArgs[index] = value; + }); + this.processedArgs = processedArgs; + } + /** + * Once we have a promise we chain, but call synchronously until then. + * + * @param {(Promise|undefined)} promise + * @param {Function} fn + * @return {(Promise|undefined)} + * @private + */ + _chainOrCall(promise, fn) { + if (promise?.then && typeof promise.then === "function") { + return promise.then(() => fn()); + } + return fn(); + } + /** + * + * @param {(Promise|undefined)} promise + * @param {string} event + * @return {(Promise|undefined)} + * @private + */ + _chainOrCallHooks(promise, event) { + let result = promise; + const hooks = []; + this._getCommandAndAncestors().reverse().filter((cmd) => cmd._lifeCycleHooks[event] !== void 0).forEach((hookedCommand) => { + hookedCommand._lifeCycleHooks[event].forEach((callback) => { + hooks.push({ hookedCommand, callback }); + }); + }); + if (event === "postAction") { + hooks.reverse(); + } + hooks.forEach((hookDetail) => { + result = this._chainOrCall(result, () => { + return hookDetail.callback(hookDetail.hookedCommand, this); + }); + }); + return result; + } + /** + * + * @param {(Promise|undefined)} promise + * @param {Command} subCommand + * @param {string} event + * @return {(Promise|undefined)} + * @private + */ + _chainOrCallSubCommandHook(promise, subCommand, event) { + let result = promise; + if (this._lifeCycleHooks[event] !== void 0) { + this._lifeCycleHooks[event].forEach((hook) => { + result = this._chainOrCall(result, () => { + return hook(this, subCommand); + }); + }); + } + return result; + } + /** + * Process arguments in context of this command. + * Returns action result, in case it is a promise. + * + * @private + */ + _parseCommand(operands, unknown) { + const parsed = this.parseOptions(unknown); + this._parseOptionsEnv(); + this._parseOptionsImplied(); + operands = operands.concat(parsed.operands); + unknown = parsed.unknown; + this.args = operands.concat(unknown); + if (operands && this._findCommand(operands[0])) { + return this._dispatchSubcommand(operands[0], operands.slice(1), unknown); + } + if (this._getHelpCommand() && operands[0] === this._getHelpCommand().name()) { + return this._dispatchHelpCommand(operands[1]); + } + if (this._defaultCommandName) { + this._outputHelpIfRequested(unknown); + return this._dispatchSubcommand( + this._defaultCommandName, + operands, + unknown + ); + } + if (this.commands.length && this.args.length === 0 && !this._actionHandler && !this._defaultCommandName) { + this.help({ error: true }); + } + this._outputHelpIfRequested(parsed.unknown); + this._checkForMissingMandatoryOptions(); + this._checkForConflictingOptions(); + const checkForUnknownOptions = () => { + if (parsed.unknown.length > 0) { + this.unknownOption(parsed.unknown[0]); + } + }; + const commandEvent = `command:${this.name()}`; + if (this._actionHandler) { + checkForUnknownOptions(); + this._processArguments(); + let promiseChain; + promiseChain = this._chainOrCallHooks(promiseChain, "preAction"); + promiseChain = this._chainOrCall( + promiseChain, + () => this._actionHandler(this.processedArgs) + ); + if (this.parent) { + promiseChain = this._chainOrCall(promiseChain, () => { + this.parent.emit(commandEvent, operands, unknown); + }); + } + promiseChain = this._chainOrCallHooks(promiseChain, "postAction"); + return promiseChain; + } + if (this.parent?.listenerCount(commandEvent)) { + checkForUnknownOptions(); + this._processArguments(); + this.parent.emit(commandEvent, operands, unknown); + } else if (operands.length) { + if (this._findCommand("*")) { + return this._dispatchSubcommand("*", operands, unknown); + } + if (this.listenerCount("command:*")) { + this.emit("command:*", operands, unknown); + } else if (this.commands.length) { + this.unknownCommand(); + } else { + checkForUnknownOptions(); + this._processArguments(); + } + } else if (this.commands.length) { + checkForUnknownOptions(); + this.help({ error: true }); + } else { + checkForUnknownOptions(); + this._processArguments(); + } + } + /** + * Find matching command. + * + * @private + * @return {Command | undefined} + */ + _findCommand(name) { + if (!name) return void 0; + return this.commands.find( + (cmd) => cmd._name === name || cmd._aliases.includes(name) + ); + } + /** + * Return an option matching `arg` if any. + * + * @param {string} arg + * @return {Option} + * @package + */ + _findOption(arg) { + return this.options.find((option) => option.is(arg)); + } + /** + * Display an error message if a mandatory option does not have a value. + * Called after checking for help flags in leaf subcommand. + * + * @private + */ + _checkForMissingMandatoryOptions() { + this._getCommandAndAncestors().forEach((cmd) => { + cmd.options.forEach((anOption) => { + if (anOption.mandatory && cmd.getOptionValue(anOption.attributeName()) === void 0) { + cmd.missingMandatoryOptionValue(anOption); + } + }); + }); + } + /** + * Display an error message if conflicting options are used together in this. + * + * @private + */ + _checkForConflictingLocalOptions() { + const definedNonDefaultOptions = this.options.filter((option) => { + const optionKey = option.attributeName(); + if (this.getOptionValue(optionKey) === void 0) { + return false; + } + return this.getOptionValueSource(optionKey) !== "default"; + }); + const optionsWithConflicting = definedNonDefaultOptions.filter( + (option) => option.conflictsWith.length > 0 + ); + optionsWithConflicting.forEach((option) => { + const conflictingAndDefined = definedNonDefaultOptions.find( + (defined) => option.conflictsWith.includes(defined.attributeName()) + ); + if (conflictingAndDefined) { + this._conflictingOption(option, conflictingAndDefined); + } + }); + } + /** + * Display an error message if conflicting options are used together. + * Called after checking for help flags in leaf subcommand. + * + * @private + */ + _checkForConflictingOptions() { + this._getCommandAndAncestors().forEach((cmd) => { + cmd._checkForConflictingLocalOptions(); + }); + } + /** + * Parse options from `argv` removing known options, + * and return argv split into operands and unknown arguments. + * + * Side effects: modifies command by storing options. Does not reset state if called again. + * + * Examples: + * + * argv => operands, unknown + * --known kkk op => [op], [] + * op --known kkk => [op], [] + * sub --unknown uuu op => [sub], [--unknown uuu op] + * sub -- --unknown uuu op => [sub --unknown uuu op], [] + * + * @param {string[]} args + * @return {{operands: string[], unknown: string[]}} + */ + parseOptions(args) { + const operands = []; + const unknown = []; + let dest = operands; + function maybeOption(arg) { + return arg.length > 1 && arg[0] === "-"; + } + const negativeNumberArg = (arg) => { + if (!/^-(\d+|\d*\.\d+)(e[+-]?\d+)?$/.test(arg)) return false; + return !this._getCommandAndAncestors().some( + (cmd) => cmd.options.map((opt) => opt.short).some((short) => /^-\d$/.test(short)) + ); + }; + let activeVariadicOption = null; + let activeGroup = null; + let i = 0; + while (i < args.length || activeGroup) { + const arg = activeGroup ?? args[i++]; + activeGroup = null; + if (arg === "--") { + if (dest === unknown) dest.push(arg); + dest.push(...args.slice(i)); + break; + } + if (activeVariadicOption && (!maybeOption(arg) || negativeNumberArg(arg))) { + this.emit(`option:${activeVariadicOption.name()}`, arg); + continue; + } + activeVariadicOption = null; + if (maybeOption(arg)) { + const option = this._findOption(arg); + if (option) { + if (option.required) { + const value = args[i++]; + if (value === void 0) this.optionMissingArgument(option); + this.emit(`option:${option.name()}`, value); + } else if (option.optional) { + let value = null; + if (i < args.length && (!maybeOption(args[i]) || negativeNumberArg(args[i]))) { + value = args[i++]; + } + this.emit(`option:${option.name()}`, value); + } else { + this.emit(`option:${option.name()}`); + } + activeVariadicOption = option.variadic ? option : null; + continue; + } + } + if (arg.length > 2 && arg[0] === "-" && arg[1] !== "-") { + const option = this._findOption(`-${arg[1]}`); + if (option) { + if (option.required || option.optional && this._combineFlagAndOptionalValue) { + this.emit(`option:${option.name()}`, arg.slice(2)); + } else { + this.emit(`option:${option.name()}`); + activeGroup = `-${arg.slice(2)}`; + } + continue; + } + } + if (/^--[^=]+=/.test(arg)) { + const index = arg.indexOf("="); + const option = this._findOption(arg.slice(0, index)); + if (option && (option.required || option.optional)) { + this.emit(`option:${option.name()}`, arg.slice(index + 1)); + continue; + } + } + if (dest === operands && maybeOption(arg) && !(this.commands.length === 0 && negativeNumberArg(arg))) { + dest = unknown; + } + if ((this._enablePositionalOptions || this._passThroughOptions) && operands.length === 0 && unknown.length === 0) { + if (this._findCommand(arg)) { + operands.push(arg); + unknown.push(...args.slice(i)); + break; + } else if (this._getHelpCommand() && arg === this._getHelpCommand().name()) { + operands.push(arg, ...args.slice(i)); + break; + } else if (this._defaultCommandName) { + unknown.push(arg, ...args.slice(i)); + break; + } + } + if (this._passThroughOptions) { + dest.push(arg, ...args.slice(i)); + break; + } + dest.push(arg); + } + return { operands, unknown }; + } + /** + * Return an object containing local option values as key-value pairs. + * + * @return {object} + */ + opts() { + if (this._storeOptionsAsProperties) { + const result = {}; + const len = this.options.length; + for (let i = 0; i < len; i++) { + const key = this.options[i].attributeName(); + result[key] = key === this._versionOptionName ? this._version : this[key]; + } + return result; + } + return this._optionValues; + } + /** + * Return an object containing merged local and global option values as key-value pairs. + * + * @return {object} + */ + optsWithGlobals() { + return this._getCommandAndAncestors().reduce( + (combinedOptions, cmd) => Object.assign(combinedOptions, cmd.opts()), + {} + ); + } + /** + * Display error message and exit (or call exitOverride). + * + * @param {string} message + * @param {object} [errorOptions] + * @param {string} [errorOptions.code] - an id string representing the error + * @param {number} [errorOptions.exitCode] - used with process.exit + */ + error(message, errorOptions) { + this._outputConfiguration.outputError( + `${message} +`, + this._outputConfiguration.writeErr + ); + if (typeof this._showHelpAfterError === "string") { + this._outputConfiguration.writeErr(`${this._showHelpAfterError} +`); + } else if (this._showHelpAfterError) { + this._outputConfiguration.writeErr("\n"); + this.outputHelp({ error: true }); + } + const config = errorOptions || {}; + const exitCode = config.exitCode || 1; + const code = config.code || "commander.error"; + this._exit(exitCode, code, message); + } + /** + * Apply any option related environment variables, if option does + * not have a value from cli or client code. + * + * @private + */ + _parseOptionsEnv() { + this.options.forEach((option) => { + if (option.envVar && option.envVar in process2.env) { + const optionKey = option.attributeName(); + if (this.getOptionValue(optionKey) === void 0 || ["default", "config", "env"].includes( + this.getOptionValueSource(optionKey) + )) { + if (option.required || option.optional) { + this.emit(`optionEnv:${option.name()}`, process2.env[option.envVar]); + } else { + this.emit(`optionEnv:${option.name()}`); + } + } + } + }); + } + /** + * Apply any implied option values, if option is undefined or default value. + * + * @private + */ + _parseOptionsImplied() { + const dualHelper = new DualOptions(this.options); + const hasCustomOptionValue = (optionKey) => { + return this.getOptionValue(optionKey) !== void 0 && !["default", "implied"].includes(this.getOptionValueSource(optionKey)); + }; + this.options.filter( + (option) => option.implied !== void 0 && hasCustomOptionValue(option.attributeName()) && dualHelper.valueFromOption( + this.getOptionValue(option.attributeName()), + option + ) + ).forEach((option) => { + Object.keys(option.implied).filter((impliedKey) => !hasCustomOptionValue(impliedKey)).forEach((impliedKey) => { + this.setOptionValueWithSource( + impliedKey, + option.implied[impliedKey], + "implied" + ); + }); + }); + } + /** + * Argument `name` is missing. + * + * @param {string} name + * @private + */ + missingArgument(name) { + const message = `error: missing required argument '${name}'`; + this.error(message, { code: "commander.missingArgument" }); + } + /** + * `Option` is missing an argument. + * + * @param {Option} option + * @private + */ + optionMissingArgument(option) { + const message = `error: option '${option.flags}' argument missing`; + this.error(message, { code: "commander.optionMissingArgument" }); + } + /** + * `Option` does not have a value, and is a mandatory option. + * + * @param {Option} option + * @private + */ + missingMandatoryOptionValue(option) { + const message = `error: required option '${option.flags}' not specified`; + this.error(message, { code: "commander.missingMandatoryOptionValue" }); + } + /** + * `Option` conflicts with another option. + * + * @param {Option} option + * @param {Option} conflictingOption + * @private + */ + _conflictingOption(option, conflictingOption) { + const findBestOptionFromValue = (option2) => { + const optionKey = option2.attributeName(); + const optionValue = this.getOptionValue(optionKey); + const negativeOption = this.options.find( + (target) => target.negate && optionKey === target.attributeName() + ); + const positiveOption = this.options.find( + (target) => !target.negate && optionKey === target.attributeName() + ); + if (negativeOption && (negativeOption.presetArg === void 0 && optionValue === false || negativeOption.presetArg !== void 0 && optionValue === negativeOption.presetArg)) { + return negativeOption; + } + return positiveOption || option2; + }; + const getErrorMessage = (option2) => { + const bestOption = findBestOptionFromValue(option2); + const optionKey = bestOption.attributeName(); + const source = this.getOptionValueSource(optionKey); + if (source === "env") { + return `environment variable '${bestOption.envVar}'`; + } + return `option '${bestOption.flags}'`; + }; + const message = `error: ${getErrorMessage(option)} cannot be used with ${getErrorMessage(conflictingOption)}`; + this.error(message, { code: "commander.conflictingOption" }); + } + /** + * Unknown option `flag`. + * + * @param {string} flag + * @private + */ + unknownOption(flag) { + if (this._allowUnknownOption) return; + let suggestion = ""; + if (flag.startsWith("--") && this._showSuggestionAfterError) { + let candidateFlags = []; + let command = this; + do { + const moreFlags = command.createHelp().visibleOptions(command).filter((option) => option.long).map((option) => option.long); + candidateFlags = candidateFlags.concat(moreFlags); + command = command.parent; + } while (command && !command._enablePositionalOptions); + suggestion = suggestSimilar(flag, candidateFlags); + } + const message = `error: unknown option '${flag}'${suggestion}`; + this.error(message, { code: "commander.unknownOption" }); + } + /** + * Excess arguments, more than expected. + * + * @param {string[]} receivedArgs + * @private + */ + _excessArguments(receivedArgs) { + if (this._allowExcessArguments) return; + const expected = this.registeredArguments.length; + const s = expected === 1 ? "" : "s"; + const forSubcommand = this.parent ? ` for '${this.name()}'` : ""; + const message = `error: too many arguments${forSubcommand}. Expected ${expected} argument${s} but got ${receivedArgs.length}.`; + this.error(message, { code: "commander.excessArguments" }); + } + /** + * Unknown command. + * + * @private + */ + unknownCommand() { + const unknownName = this.args[0]; + let suggestion = ""; + if (this._showSuggestionAfterError) { + const candidateNames = []; + this.createHelp().visibleCommands(this).forEach((command) => { + candidateNames.push(command.name()); + if (command.alias()) candidateNames.push(command.alias()); + }); + suggestion = suggestSimilar(unknownName, candidateNames); + } + const message = `error: unknown command '${unknownName}'${suggestion}`; + this.error(message, { code: "commander.unknownCommand" }); + } + /** + * Get or set the program version. + * + * This method auto-registers the "-V, --version" option which will print the version number. + * + * You can optionally supply the flags and description to override the defaults. + * + * @param {string} [str] + * @param {string} [flags] + * @param {string} [description] + * @return {(this | string | undefined)} `this` command for chaining, or version string if no arguments + */ + version(str, flags, description) { + if (str === void 0) return this._version; + this._version = str; + flags = flags || "-V, --version"; + description = description || "output the version number"; + const versionOption = this.createOption(flags, description); + this._versionOptionName = versionOption.attributeName(); + this._registerOption(versionOption); + this.on("option:" + versionOption.name(), () => { + this._outputConfiguration.writeOut(`${str} +`); + this._exit(0, "commander.version", str); + }); + return this; + } + /** + * Set the description. + * + * @param {string} [str] + * @param {object} [argsDescription] + * @return {(string|Command)} + */ + description(str, argsDescription) { + if (str === void 0 && argsDescription === void 0) + return this._description; + this._description = str; + if (argsDescription) { + this._argsDescription = argsDescription; + } + return this; + } + /** + * Set the summary. Used when listed as subcommand of parent. + * + * @param {string} [str] + * @return {(string|Command)} + */ + summary(str) { + if (str === void 0) return this._summary; + this._summary = str; + return this; + } + /** + * Set an alias for the command. + * + * You may call more than once to add multiple aliases. Only the first alias is shown in the auto-generated help. + * + * @param {string} [alias] + * @return {(string|Command)} + */ + alias(alias) { + if (alias === void 0) return this._aliases[0]; + let command = this; + if (this.commands.length !== 0 && this.commands[this.commands.length - 1]._executableHandler) { + command = this.commands[this.commands.length - 1]; + } + if (alias === command._name) + throw new Error("Command alias can't be the same as its name"); + const matchingCommand = this.parent?._findCommand(alias); + if (matchingCommand) { + const existingCmd = [matchingCommand.name()].concat(matchingCommand.aliases()).join("|"); + throw new Error( + `cannot add alias '${alias}' to command '${this.name()}' as already have command '${existingCmd}'` + ); + } + command._aliases.push(alias); + return this; + } + /** + * Set aliases for the command. + * + * Only the first alias is shown in the auto-generated help. + * + * @param {string[]} [aliases] + * @return {(string[]|Command)} + */ + aliases(aliases) { + if (aliases === void 0) return this._aliases; + aliases.forEach((alias) => this.alias(alias)); + return this; + } + /** + * Set / get the command usage `str`. + * + * @param {string} [str] + * @return {(string|Command)} + */ + usage(str) { + if (str === void 0) { + if (this._usage) return this._usage; + const args = this.registeredArguments.map((arg) => { + return humanReadableArgName(arg); + }); + return [].concat( + this.options.length || this._helpOption !== null ? "[options]" : [], + this.commands.length ? "[command]" : [], + this.registeredArguments.length ? args : [] + ).join(" "); + } + this._usage = str; + return this; + } + /** + * Get or set the name of the command. + * + * @param {string} [str] + * @return {(string|Command)} + */ + name(str) { + if (str === void 0) return this._name; + this._name = str; + return this; + } + /** + * Set/get the help group heading for this subcommand in parent command's help. + * + * @param {string} [heading] + * @return {Command | string} + */ + helpGroup(heading) { + if (heading === void 0) return this._helpGroupHeading ?? ""; + this._helpGroupHeading = heading; + return this; + } + /** + * Set/get the default help group heading for subcommands added to this command. + * (This does not override a group set directly on the subcommand using .helpGroup().) + * + * @example + * program.commandsGroup('Development Commands:); + * program.command('watch')... + * program.command('lint')... + * ... + * + * @param {string} [heading] + * @returns {Command | string} + */ + commandsGroup(heading) { + if (heading === void 0) return this._defaultCommandGroup ?? ""; + this._defaultCommandGroup = heading; + return this; + } + /** + * Set/get the default help group heading for options added to this command. + * (This does not override a group set directly on the option using .helpGroup().) + * + * @example + * program + * .optionsGroup('Development Options:') + * .option('-d, --debug', 'output extra debugging') + * .option('-p, --profile', 'output profiling information') + * + * @param {string} [heading] + * @returns {Command | string} + */ + optionsGroup(heading) { + if (heading === void 0) return this._defaultOptionGroup ?? ""; + this._defaultOptionGroup = heading; + return this; + } + /** + * @param {Option} option + * @private + */ + _initOptionGroup(option) { + if (this._defaultOptionGroup && !option.helpGroupHeading) + option.helpGroup(this._defaultOptionGroup); + } + /** + * @param {Command} cmd + * @private + */ + _initCommandGroup(cmd) { + if (this._defaultCommandGroup && !cmd.helpGroup()) + cmd.helpGroup(this._defaultCommandGroup); + } + /** + * Set the name of the command from script filename, such as process.argv[1], + * or require.main.filename, or __filename. + * + * (Used internally and public although not documented in README.) + * + * @example + * program.nameFromFilename(require.main.filename); + * + * @param {string} filename + * @return {Command} + */ + nameFromFilename(filename) { + this._name = path20.basename(filename, path20.extname(filename)); + return this; + } + /** + * Get or set the directory for searching for executable subcommands of this command. + * + * @example + * program.executableDir(__dirname); + * // or + * program.executableDir('subcommands'); + * + * @param {string} [path] + * @return {(string|null|Command)} + */ + executableDir(path21) { + if (path21 === void 0) return this._executableDir; + this._executableDir = path21; + return this; + } + /** + * Return program help documentation. + * + * @param {{ error: boolean }} [contextOptions] - pass {error:true} to wrap for stderr instead of stdout + * @return {string} + */ + helpInformation(contextOptions) { + const helper = this.createHelp(); + const context = this._getOutputContext(contextOptions); + helper.prepareContext({ + error: context.error, + helpWidth: context.helpWidth, + outputHasColors: context.hasColors + }); + const text = helper.formatHelp(this, helper); + if (context.hasColors) return text; + return this._outputConfiguration.stripColor(text); + } + /** + * @typedef HelpContext + * @type {object} + * @property {boolean} error + * @property {number} helpWidth + * @property {boolean} hasColors + * @property {function} write - includes stripColor if needed + * + * @returns {HelpContext} + * @private + */ + _getOutputContext(contextOptions) { + contextOptions = contextOptions || {}; + const error = !!contextOptions.error; + let baseWrite; + let hasColors; + let helpWidth; + if (error) { + baseWrite = (str) => this._outputConfiguration.writeErr(str); + hasColors = this._outputConfiguration.getErrHasColors(); + helpWidth = this._outputConfiguration.getErrHelpWidth(); + } else { + baseWrite = (str) => this._outputConfiguration.writeOut(str); + hasColors = this._outputConfiguration.getOutHasColors(); + helpWidth = this._outputConfiguration.getOutHelpWidth(); + } + const write = (str) => { + if (!hasColors) str = this._outputConfiguration.stripColor(str); + return baseWrite(str); + }; + return { error, write, hasColors, helpWidth }; + } + /** + * Output help information for this command. + * + * Outputs built-in help, and custom text added using `.addHelpText()`. + * + * @param {{ error: boolean } | Function} [contextOptions] - pass {error:true} to write to stderr instead of stdout + */ + outputHelp(contextOptions) { + let deprecatedCallback; + if (typeof contextOptions === "function") { + deprecatedCallback = contextOptions; + contextOptions = void 0; + } + const outputContext = this._getOutputContext(contextOptions); + const eventContext = { + error: outputContext.error, + write: outputContext.write, + command: this + }; + this._getCommandAndAncestors().reverse().forEach((command) => command.emit("beforeAllHelp", eventContext)); + this.emit("beforeHelp", eventContext); + let helpInformation = this.helpInformation({ error: outputContext.error }); + if (deprecatedCallback) { + helpInformation = deprecatedCallback(helpInformation); + if (typeof helpInformation !== "string" && !Buffer.isBuffer(helpInformation)) { + throw new Error("outputHelp callback must return a string or a Buffer"); + } + } + outputContext.write(helpInformation); + if (this._getHelpOption()?.long) { + this.emit(this._getHelpOption().long); + } + this.emit("afterHelp", eventContext); + this._getCommandAndAncestors().forEach( + (command) => command.emit("afterAllHelp", eventContext) + ); + } + /** + * You can pass in flags and a description to customise the built-in help option. + * Pass in false to disable the built-in help option. + * + * @example + * program.helpOption('-?, --help' 'show help'); // customise + * program.helpOption(false); // disable + * + * @param {(string | boolean)} flags + * @param {string} [description] + * @return {Command} `this` command for chaining + */ + helpOption(flags, description) { + if (typeof flags === "boolean") { + if (flags) { + if (this._helpOption === null) this._helpOption = void 0; + if (this._defaultOptionGroup) { + this._initOptionGroup(this._getHelpOption()); + } + } else { + this._helpOption = null; + } + return this; + } + this._helpOption = this.createOption( + flags ?? "-h, --help", + description ?? "display help for command" + ); + if (flags || description) this._initOptionGroup(this._helpOption); + return this; + } + /** + * Lazy create help option. + * Returns null if has been disabled with .helpOption(false). + * + * @returns {(Option | null)} the help option + * @package + */ + _getHelpOption() { + if (this._helpOption === void 0) { + this.helpOption(void 0, void 0); + } + return this._helpOption; + } + /** + * Supply your own option to use for the built-in help option. + * This is an alternative to using helpOption() to customise the flags and description etc. + * + * @param {Option} option + * @return {Command} `this` command for chaining + */ + addHelpOption(option) { + this._helpOption = option; + this._initOptionGroup(option); + return this; + } + /** + * Output help information and exit. + * + * Outputs built-in help, and custom text added using `.addHelpText()`. + * + * @param {{ error: boolean }} [contextOptions] - pass {error:true} to write to stderr instead of stdout + */ + help(contextOptions) { + this.outputHelp(contextOptions); + let exitCode = Number(process2.exitCode ?? 0); + if (exitCode === 0 && contextOptions && typeof contextOptions !== "function" && contextOptions.error) { + exitCode = 1; + } + this._exit(exitCode, "commander.help", "(outputHelp)"); + } + /** + * // Do a little typing to coordinate emit and listener for the help text events. + * @typedef HelpTextEventContext + * @type {object} + * @property {boolean} error + * @property {Command} command + * @property {function} write + */ + /** + * Add additional text to be displayed with the built-in help. + * + * Position is 'before' or 'after' to affect just this command, + * and 'beforeAll' or 'afterAll' to affect this command and all its subcommands. + * + * @param {string} position - before or after built-in help + * @param {(string | Function)} text - string to add, or a function returning a string + * @return {Command} `this` command for chaining + */ + addHelpText(position, text) { + const allowedValues = ["beforeAll", "before", "after", "afterAll"]; + if (!allowedValues.includes(position)) { + throw new Error(`Unexpected value for position to addHelpText. +Expecting one of '${allowedValues.join("', '")}'`); + } + const helpEvent = `${position}Help`; + this.on(helpEvent, (context) => { + let helpStr; + if (typeof text === "function") { + helpStr = text({ error: context.error, command: context.command }); + } else { + helpStr = text; + } + if (helpStr) { + context.write(`${helpStr} +`); + } + }); + return this; + } + /** + * Output help information if help flags specified + * + * @param {Array} args - array of options to search for help flags + * @private + */ + _outputHelpIfRequested(args) { + const helpOption = this._getHelpOption(); + const helpRequested = helpOption && args.find((arg) => helpOption.is(arg)); + if (helpRequested) { + this.outputHelp(); + this._exit(0, "commander.helpDisplayed", "(outputHelp)"); + } + } + }; + function incrementNodeInspectorPort(args) { + return args.map((arg) => { + if (!arg.startsWith("--inspect")) { + return arg; + } + let debugOption; + let debugHost = "127.0.0.1"; + let debugPort = "9229"; + let match; + if ((match = arg.match(/^(--inspect(-brk)?)$/)) !== null) { + debugOption = match[1]; + } else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+)$/)) !== null) { + debugOption = match[1]; + if (/^\d+$/.test(match[3])) { + debugPort = match[3]; + } else { + debugHost = match[3]; + } + } else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+):(\d+)$/)) !== null) { + debugOption = match[1]; + debugHost = match[3]; + debugPort = match[4]; + } + if (debugOption && debugPort !== "0") { + return `${debugOption}=${debugHost}:${parseInt(debugPort) + 1}`; + } + return arg; + }); + } + function useColor() { + if (process2.env.NO_COLOR || process2.env.FORCE_COLOR === "0" || process2.env.FORCE_COLOR === "false") + return false; + if (process2.env.FORCE_COLOR || process2.env.CLICOLOR_FORCE !== void 0) + return true; + return void 0; + } + exports2.Command = Command2; + exports2.useColor = useColor; + } +}); + +// node_modules/commander/index.js +var require_commander = __commonJS({ + "node_modules/commander/index.js"(exports2) { + var { Argument: Argument2 } = require_argument(); + var { Command: Command2 } = require_command(); + var { CommanderError: CommanderError2, InvalidArgumentError: InvalidArgumentError2 } = require_error(); + var { Help: Help2 } = require_help(); + var { Option: Option2 } = require_option(); + exports2.program = new Command2(); + exports2.createCommand = (name) => new Command2(name); + exports2.createOption = (flags, description) => new Option2(flags, description); + exports2.createArgument = (name, description) => new Argument2(name, description); + exports2.Command = Command2; + exports2.Option = Option2; + exports2.Argument = Argument2; + exports2.Help = Help2; + exports2.CommanderError = CommanderError2; + exports2.InvalidArgumentError = InvalidArgumentError2; + exports2.InvalidOptionArgumentError = InvalidArgumentError2; + } +}); + +// src/directory.ts +function getCodeGraphDir(projectRoot) { + return path.join(projectRoot, CODEGRAPH_DIR); +} +function isInitialized(projectRoot) { + const codegraphDir = getCodeGraphDir(projectRoot); + if (!fs.existsSync(codegraphDir) || !fs.statSync(codegraphDir).isDirectory()) { + return false; + } + const dbPath = path.join(codegraphDir, "codegraph.db"); + return fs.existsSync(dbPath); +} +function findNearestCodeGraphRoot(startPath) { + let current = path.resolve(startPath); + const root = path.parse(current).root; + while (current !== root) { + if (isInitialized(current)) { + return current; + } + const parent = path.dirname(current); + if (parent === current) break; + current = parent; + } + if (isInitialized(current)) { + return current; + } + return null; +} +function createDirectory(projectRoot) { + const codegraphDir = getCodeGraphDir(projectRoot); + const dbPath = path.join(codegraphDir, "codegraph.db"); + if (fs.existsSync(dbPath)) { + throw new Error(`CodeGraph already initialized in ${projectRoot}`); + } + fs.mkdirSync(codegraphDir, { recursive: true }); + const gitignorePath = path.join(codegraphDir, ".gitignore"); + if (!fs.existsSync(gitignorePath)) { + const gitignoreContent = `# CodeGraph data files +# These are local to each machine and should not be committed + +# Database +*.db +*.db-wal +*.db-shm + +# Cache +cache/ + +# Logs +*.log + +# Hook markers +.dirty +`; + fs.writeFileSync(gitignorePath, gitignoreContent, "utf-8"); + } +} +function removeDirectory(projectRoot) { + const codegraphDir = getCodeGraphDir(projectRoot); + if (!fs.existsSync(codegraphDir)) { + return; + } + const lstat = fs.lstatSync(codegraphDir); + if (lstat.isSymbolicLink()) { + fs.unlinkSync(codegraphDir); + return; + } + if (!lstat.isDirectory()) { + fs.unlinkSync(codegraphDir); + return; + } + fs.rmSync(codegraphDir, { recursive: true, force: true }); +} +function validateDirectory(projectRoot) { + const errors = []; + const codegraphDir = getCodeGraphDir(projectRoot); + if (!fs.existsSync(codegraphDir)) { + errors.push("CodeGraph directory does not exist"); + return { valid: false, errors }; + } + if (!fs.statSync(codegraphDir).isDirectory()) { + errors.push(".codegraph exists but is not a directory"); + return { valid: false, errors }; + } + const gitignorePath = path.join(codegraphDir, ".gitignore"); + if (!fs.existsSync(gitignorePath)) { + try { + const gitignoreContent = `# CodeGraph data files +# These are local to each machine and should not be committed + +# Database +*.db +*.db-wal +*.db-shm + +# Cache +cache/ + +# Logs +*.log + +# Hook markers +.dirty +`; + fs.writeFileSync(gitignorePath, gitignoreContent, "utf-8"); + } catch { + errors.push(".gitignore missing in .codegraph directory and could not be created"); + } + } + return { + valid: errors.length === 0, + errors + }; +} +var fs, path, CODEGRAPH_DIR; +var init_directory = __esm({ + "src/directory.ts"() { + "use strict"; + fs = __toESM(require("fs")); + path = __toESM(require("path")); + CODEGRAPH_DIR = ".codegraph"; + } +}); + +// src/projects.ts +function nameFromPath(relPath) { + const segments = relPath.replace(/\\/g, "/").split("/").filter(Boolean); + return segments[segments.length - 1] ?? relPath; +} +function parseEntry(raw) { + if (typeof raw === "string" && raw) { + return { name: nameFromPath(raw), path: raw }; + } + if (raw && typeof raw === "object") { + const obj = raw; + const p = typeof obj.path === "string" ? obj.path : null; + if (!p) return null; + const n = typeof obj.name === "string" && obj.name ? obj.name : nameFromPath(p); + return { name: n, path: p }; + } + return null; +} +function getProjectsPath(projectRoot) { + return path2.join(projectRoot, CODEGRAPH_DIR, PROJECTS_FILENAME); +} +function loadProjectEntries(projectRoot) { + const filePath = getProjectsPath(projectRoot); + try { + if (!fs2.existsSync(filePath)) return []; + const content = fs2.readFileSync(filePath, "utf-8"); + const parsed = JSON.parse(content); + if (!Array.isArray(parsed)) return []; + return parsed.map(parseEntry).filter((e) => e !== null); + } catch (err) { + process.stderr.write( + `[CodeGraph] Failed to load projects.json: ${err instanceof Error ? err.message : String(err)} +` + ); + return []; + } +} +function loadProjects(projectRoot) { + return loadProjectEntries(projectRoot).map((e) => e.path); +} +function saveProjects(projectRoot, entries) { + const filePath = getProjectsPath(projectRoot); + const dir = path2.dirname(filePath); + if (!fs2.existsSync(dir)) { + fs2.mkdirSync(dir, { recursive: true }); + } + const byPath = /* @__PURE__ */ new Map(); + for (const e of entries) byPath.set(e.path, e); + const unique = [...byPath.values()].sort((a, b) => a.name.localeCompare(b.name)); + const content = JSON.stringify(unique, null, 2); + const tmpPath = filePath + ".tmp"; + fs2.writeFileSync(tmpPath, content, "utf-8"); + fs2.renameSync(tmpPath, filePath); +} +function addProject(projectRoot, projectPath, name) { + const entries = loadProjectEntries(projectRoot); + const resolvedName = name || nameFromPath(projectPath); + const existing = entries.findIndex((e) => e.path === projectPath); + if (existing >= 0) { + entries[existing] = { name: resolvedName, path: projectPath }; + } else { + entries.push({ name: resolvedName, path: projectPath }); + } + saveProjects(projectRoot, entries); + return entries; +} +function removeProject(projectRoot, pathOrName) { + const entries = loadProjectEntries(projectRoot).filter( + (e) => e.path !== pathOrName && e.name !== pathOrName + ); + saveProjects(projectRoot, entries); + return entries; +} +function scanForProjects(root, maxDepth = 3) { + const results = []; + const queue = [[root, 0]]; + while (queue.length > 0) { + const [currentDir, depth] = queue.shift(); + if (depth > 0 && isInitialized(currentDir)) { + const relPath = path2.relative(root, currentDir).replace(/\\/g, "/"); + results.push({ name: nameFromPath(relPath), path: relPath }); + } + if (depth > maxDepth) continue; + try { + const dirEntries = fs2.readdirSync(currentDir, { withFileTypes: true }); + for (const entry of dirEntries) { + if (entry.isDirectory() && !SCAN_SKIP_DIRS.has(entry.name)) { + queue.push([path2.join(currentDir, entry.name), depth + 1]); + } + } + } catch { + } + } + const byPath = /* @__PURE__ */ new Map(); + for (const e of results) byPath.set(e.path, e); + return [...byPath.values()].sort((a, b) => a.name.localeCompare(b.name)); +} +function findNearestMonorepoRoot(startPath) { + let current = path2.resolve(startPath); + const fsRoot = path2.parse(current).root; + while (current !== fsRoot) { + if (loadProjectEntries(current).length > 0) return current; + const parent = path2.dirname(current); + if (parent === current) break; + current = parent; + } + if (loadProjectEntries(current).length > 0) return current; + return null; +} +function syncProjects(root, maxDepth) { + const existing = loadProjectEntries(root); + const discovered = scanForProjects(root, maxDepth); + const byPath = /* @__PURE__ */ new Map(); + for (const e of discovered) byPath.set(e.path, e); + for (const e of existing) byPath.set(e.path, e); + const merged = [...byPath.values()].sort((a, b) => a.name.localeCompare(b.name)); + saveProjects(root, merged); + return merged; +} +var fs2, path2, PROJECTS_FILENAME, SCAN_SKIP_DIRS; +var init_projects = __esm({ + "src/projects.ts"() { + "use strict"; + fs2 = __toESM(require("fs")); + path2 = __toESM(require("path")); + init_directory(); + PROJECTS_FILENAME = "projects.json"; + SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([".codegraph", ".git", "node_modules"]); + } +}); + +// src/ui/shimmer-progress.ts +var shimmer_progress_exports = {}; +__export(shimmer_progress_exports, { + createShimmerProgress: () => createShimmerProgress +}); +function createShimmerProgress() { + let lastPhase = ""; + const workerPath = path3.join(__dirname, "shimmer-worker.js"); + const worker = new import_worker_threads.Worker(workerPath, { + workerData: { startTime: Date.now() } + }); + return { + onProgress(progress) { + const phaseName = PHASE_NAMES[progress.phase] || progress.phase; + if (progress.phase !== lastPhase && lastPhase) { + worker.postMessage({ type: "finish-phase" }); + } + lastPhase = progress.phase; + let percent = -1; + let count = 0; + if (progress.total > 0) { + percent = Math.round(progress.current / progress.total * 100); + } else if (progress.current > 0) { + count = progress.current; + } + worker.postMessage({ + type: "update", + phase: progress.phase, + phaseName, + percent, + count + }); + }, + stop() { + return new Promise((resolve10) => { + const timeout = setTimeout(() => { + worker.terminate().then(() => resolve10()); + }, 2e3); + worker.on("message", (msg) => { + if (msg.type === "stopped") { + clearTimeout(timeout); + worker.terminate().then(() => resolve10()); + } + }); + worker.postMessage({ type: "stop" }); + }); + } + }; +} +var import_worker_threads, path3, PHASE_NAMES; +var init_shimmer_progress = __esm({ + "src/ui/shimmer-progress.ts"() { + "use strict"; + import_worker_threads = require("worker_threads"); + path3 = __toESM(require("path")); + PHASE_NAMES = { + scanning: "Scanning files", + parsing: "Parsing code", + storing: "Storing data", + resolving: "Resolving refs" + }; + } +}); + +// src/db/sqlite-adapter.ts +function buildWasmFallbackBanner(nativeError) { + const sep3 = "\u2500".repeat(72); + const lines = [ + sep3, + "[CodeGraph] WASM SQLite fallback active (better-sqlite3 unavailable)", + sep3, + "Indexing and sync will be 5-10x slower than the native backend.", + "", + "Fix on macOS:", + " xcode-select --install # install C build tools", + " npm rebuild better-sqlite3 # rebuild native binding for current Node", + "", + "Fix on Linux:", + " sudo apt install build-essential python3 make # Debian/Ubuntu", + ' # or: sudo yum groupinstall "Development Tools" # RHEL/Fedora', + " npm rebuild better-sqlite3", + "", + "Or force-include as a hard dependency on any platform:", + " npm install better-sqlite3 --save", + "", + "Verify after fix: `codegraph status` should show `Backend: native`." + ]; + if (nativeError) { + lines.push("", `Native load error: ${nativeError}`); + } + lines.push(sep3); + return lines.join("\n"); +} +function translateNamedParams(sql) { + const paramOrder = []; + const rewritten = sql.replace(/@(\w+)/g, (_match, name) => { + paramOrder.push(name); + return "?"; + }); + if (paramOrder.length === 0) { + return { sql, paramOrder: null }; + } + return { sql: rewritten, paramOrder }; +} +function resolveParams(params, paramOrder) { + if (params.length === 0) return void 0; + if (paramOrder && params.length === 1 && params[0] !== null && typeof params[0] === "object" && !Array.isArray(params[0]) && !(params[0] instanceof Buffer) && !(params[0] instanceof Uint8Array)) { + const obj = params[0]; + return paramOrder.map((name) => obj[name]); + } + if (params.length === 1) return params[0]; + return params; +} +function createDatabase(dbPath) { + let nativeError; + let wasmError; + try { + const Database = require("better-sqlite3"); + const db = new Database(dbPath); + return { db, backend: "native" }; + } catch (error) { + nativeError = error instanceof Error ? error.message : String(error); + } + try { + const db = new WasmDatabaseAdapter(dbPath); + console.warn(buildWasmFallbackBanner(nativeError)); + return { db, backend: "wasm" }; + } catch (error) { + wasmError = error instanceof Error ? error.message : String(error); + } + throw new Error( + `Failed to load any SQLite backend. + Native (better-sqlite3): ${nativeError} + WASM (node-sqlite3-wasm): ${wasmError}` + ); +} +var WASM_FALLBACK_FIX_RECIPE, WasmDatabaseAdapter; +var init_sqlite_adapter = __esm({ + "src/db/sqlite-adapter.ts"() { + "use strict"; + WASM_FALLBACK_FIX_RECIPE = "`xcode-select --install` (macOS) or `apt install build-essential` (Debian/Ubuntu), then `npm rebuild better-sqlite3`, or `npm install better-sqlite3 --save` to force-include it."; + WasmDatabaseAdapter = class { + _db; + // Track raw WASM statements so we can finalize them on close. + // node-sqlite3-wasm won't release its file lock if statements are left open. + _openStmts = /* @__PURE__ */ new Set(); + constructor(dbPath) { + const { Database } = require("node-sqlite3-wasm"); + this._db = new Database(dbPath); + } + get open() { + return this._db.isOpen; + } + prepare(sql) { + const { sql: rewrittenSql, paramOrder } = translateNamedParams(sql); + const stmt = this._db.prepare(rewrittenSql); + this._openStmts.add(stmt); + return { + run(...params) { + const resolved = resolveParams(params, paramOrder); + const result = resolved !== void 0 ? stmt.run(resolved) : stmt.run(); + return { + changes: result?.changes ?? 0, + lastInsertRowid: result?.lastInsertRowid ?? 0 + }; + }, + get(...params) { + const resolved = resolveParams(params, paramOrder); + return resolved !== void 0 ? stmt.get(resolved) : stmt.get(); + }, + all(...params) { + const resolved = resolveParams(params, paramOrder); + return resolved !== void 0 ? stmt.all(resolved) : stmt.all(); + } + }; + } + exec(sql) { + this._db.exec(sql); + } + pragma(str) { + const trimmed = str.trim(); + if (trimmed.includes("=")) { + const eqIdx = trimmed.indexOf("="); + const key = trimmed.substring(0, eqIdx).trim(); + const value = trimmed.substring(eqIdx + 1).trim(); + if (key === "journal_mode" && value.toUpperCase() === "WAL") { + this._db.exec("PRAGMA journal_mode = DELETE"); + return; + } + if (key === "mmap_size") { + return; + } + if (key === "synchronous" && value.toUpperCase() === "NORMAL") { + this._db.exec("PRAGMA synchronous = FULL"); + return; + } + this._db.exec(`PRAGMA ${key} = ${value}`); + return; + } + const stmt = this._db.prepare(`PRAGMA ${trimmed}`); + const result = stmt.get(); + stmt.finalize(); + return result; + } + transaction(fn) { + return (...args) => { + this._db.exec("BEGIN"); + try { + const result = fn(...args); + this._db.exec("COMMIT"); + return result; + } catch (error) { + this._db.exec("ROLLBACK"); + throw error; + } + }; + } + close() { + for (const stmt of this._openStmts) { + try { + stmt.finalize(); + } catch { + } + } + this._openStmts.clear(); + this._db.close(); + } + }; + } +}); + +// src/db/migrations.ts +function getCurrentVersion(db) { + try { + const row = db.prepare("SELECT MAX(version) as version FROM schema_versions").get(); + return row?.version ?? 0; + } catch { + return 0; + } +} +function recordMigration(db, version, description) { + db.prepare( + "INSERT INTO schema_versions (version, applied_at, description) VALUES (?, ?, ?)" + ).run(version, Date.now(), description); +} +function runMigrations(db, fromVersion) { + const pending = migrations.filter((m) => m.version > fromVersion); + if (pending.length === 0) { + return; + } + pending.sort((a, b) => a.version - b.version); + for (const migration of pending) { + db.transaction(() => { + migration.up(db); + recordMigration(db, migration.version, migration.description); + })(); + } +} +var CURRENT_SCHEMA_VERSION, migrations; +var init_migrations = __esm({ + "src/db/migrations.ts"() { + "use strict"; + CURRENT_SCHEMA_VERSION = 4; + migrations = [ + { + version: 2, + description: "Add project metadata, provenance tracking, and unresolved ref context", + up: (db) => { + db.exec(` + CREATE TABLE IF NOT EXISTS unresolved_refs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + from_node_id TEXT NOT NULL, + reference_name TEXT NOT NULL, + reference_kind TEXT NOT NULL, + line INTEGER NOT NULL, + col INTEGER NOT NULL, + candidates TEXT, + FOREIGN KEY (from_node_id) REFERENCES nodes(id) ON DELETE CASCADE + ); + CREATE TABLE IF NOT EXISTS project_metadata ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + updated_at INTEGER NOT NULL + ); + `); + const unresCols = new Set( + db.prepare("PRAGMA table_info(unresolved_refs)").all().map( + (c) => c.name + ) + ); + if (!unresCols.has("file_path")) { + db.exec(`ALTER TABLE unresolved_refs ADD COLUMN file_path TEXT NOT NULL DEFAULT '';`); + } + if (!unresCols.has("language")) { + db.exec( + `ALTER TABLE unresolved_refs ADD COLUMN language TEXT NOT NULL DEFAULT 'unknown';` + ); + } + const edgeCols = new Set( + db.prepare("PRAGMA table_info(edges)").all().map( + (c) => c.name + ) + ); + if (!edgeCols.has("provenance")) { + db.exec(`ALTER TABLE edges ADD COLUMN provenance TEXT DEFAULT NULL;`); + } + db.exec(` + CREATE INDEX IF NOT EXISTS idx_unresolved_file_path ON unresolved_refs(file_path); + CREATE INDEX IF NOT EXISTS idx_edges_provenance ON edges(provenance); + `); + } + }, + { + version: 3, + description: "Add lower(name) expression index for memory-efficient case-insensitive lookups", + up: (db) => { + db.exec(` + CREATE INDEX IF NOT EXISTS idx_nodes_lower_name ON nodes(lower(name)); + `); + } + }, + { + version: 4, + description: "Drop redundant idx_edges_source / idx_edges_target (covered by source_kind / target_kind composites)", + up: (db) => { + db.exec(` + DROP INDEX IF EXISTS idx_edges_source; + DROP INDEX IF EXISTS idx_edges_target; + `); + } + } + ]; + } +}); + +// src/db/index.ts +function getDatabasePath(projectRoot) { + return path4.join(projectRoot, ".codegraph", DATABASE_FILENAME); +} +var fs3, path4, DatabaseConnection, DATABASE_FILENAME; +var init_db = __esm({ + "src/db/index.ts"() { + "use strict"; + init_sqlite_adapter(); + fs3 = __toESM(require("fs")); + path4 = __toESM(require("path")); + init_migrations(); + init_sqlite_adapter(); + DatabaseConnection = class _DatabaseConnection { + db; + dbPath; + backend; + constructor(db, dbPath, backend) { + this.db = db; + this.dbPath = dbPath; + this.backend = backend; + } + /** + * Initialize a new database at the given path + */ + static initialize(dbPath) { + const dir = path4.dirname(dbPath); + if (!fs3.existsSync(dir)) { + fs3.mkdirSync(dir, { recursive: true }); + } + const { db, backend } = createDatabase(dbPath); + db.pragma("foreign_keys = ON"); + db.pragma("journal_mode = WAL"); + db.pragma("busy_timeout = 120000"); + db.pragma("synchronous = NORMAL"); + db.pragma("cache_size = -64000"); + db.pragma("temp_store = MEMORY"); + db.pragma("mmap_size = 268435456"); + const schemaPath = path4.join(__dirname, "schema.sql"); + const schema = fs3.readFileSync(schemaPath, "utf-8"); + db.exec(schema); + const currentVersion = getCurrentVersion(db); + if (currentVersion < CURRENT_SCHEMA_VERSION) { + db.prepare( + "INSERT OR IGNORE INTO schema_versions (version, applied_at, description) VALUES (?, ?, ?)" + ).run(CURRENT_SCHEMA_VERSION, Date.now(), "Initial schema includes all migrations"); + } + return new _DatabaseConnection(db, dbPath, backend); + } + /** + * Open an existing database + */ + static open(dbPath) { + if (!fs3.existsSync(dbPath)) { + throw new Error(`Database not found: ${dbPath}`); + } + const { db, backend } = createDatabase(dbPath); + db.pragma("foreign_keys = ON"); + db.pragma("journal_mode = WAL"); + db.pragma("busy_timeout = 120000"); + db.pragma("synchronous = NORMAL"); + db.pragma("cache_size = -64000"); + db.pragma("temp_store = MEMORY"); + db.pragma("mmap_size = 268435456"); + const conn = new _DatabaseConnection(db, dbPath, backend); + const currentVersion = getCurrentVersion(db); + if (currentVersion < CURRENT_SCHEMA_VERSION) { + runMigrations(db, currentVersion); + } + return conn; + } + /** + * Get the underlying database instance + */ + getDb() { + return this.db; + } + /** + * Get the SQLite backend serving this connection. Per-instance so + * MCP cross-project queries report the right backend even when + * multiple project DBs are open in the same process. + */ + getBackend() { + return this.backend; + } + /** + * Get database file path + */ + getPath() { + return this.dbPath; + } + /** + * Get current schema version + */ + getSchemaVersion() { + const row = this.db.prepare("SELECT version, applied_at, description FROM schema_versions ORDER BY version DESC LIMIT 1").get(); + if (!row) return null; + return { + version: row.version, + appliedAt: row.applied_at, + description: row.description ?? void 0 + }; + } + /** + * Execute a function within a transaction + */ + transaction(fn) { + return this.db.transaction(fn)(); + } + /** + * Get database file size in bytes + */ + getSize() { + const stats = fs3.statSync(this.dbPath); + return stats.size; + } + /** + * Optimize database (vacuum and analyze) + */ + optimize() { + this.db.exec("VACUUM"); + this.db.exec("ANALYZE"); + } + /** + * Close the database connection + */ + close() { + this.db.close(); + } + /** + * Check if the database connection is open + */ + isOpen() { + return this.db.open; + } + }; + DATABASE_FILENAME = "codegraph.db"; + } +}); + +// src/utils.ts +function validatePathWithinRoot(projectRoot, filePath) { + const resolved = path5.resolve(projectRoot, filePath); + const normalizedRoot = path5.resolve(projectRoot); + if (!resolved.startsWith(normalizedRoot + path5.sep) && resolved !== normalizedRoot) { + return null; + } + return resolved; +} +function safeJsonParse(value, fallback) { + try { + return JSON.parse(value); + } catch { + return fallback; + } +} +function clamp(value, min, max) { + return Math.max(min, Math.min(max, value)); +} +function normalizePath(filePath) { + return filePath.replace(/\\/g, "/"); +} +async function processInBatches(items, batchSize, processor, onBatchComplete) { + const results = []; + for (let i = 0; i < items.length; i += batchSize) { + const batch = items.slice(i, Math.min(i + batchSize, items.length)); + const batchResults = await Promise.all( + batch.map((item, idx) => processor(item, i + idx)) + ); + results.push(...batchResults); + if (onBatchComplete) { + onBatchComplete(Math.min(i + batchSize, items.length), items.length); + } + if (global.gc) { + global.gc(); + } + } + return results; +} +function debounce(fn, delay) { + let timeoutId = null; + return (...args) => { + if (timeoutId) { + clearTimeout(timeoutId); + } + timeoutId = setTimeout(() => { + fn(...args); + timeoutId = null; + }, delay); + }; +} +function throttle(fn, limit) { + let lastCall = 0; + let timeoutId = null; + return (...args) => { + const now = Date.now(); + const remaining = limit - (now - lastCall); + if (remaining <= 0) { + if (timeoutId) { + clearTimeout(timeoutId); + timeoutId = null; + } + lastCall = now; + fn(...args); + } else if (!timeoutId) { + timeoutId = setTimeout(() => { + lastCall = Date.now(); + timeoutId = null; + fn(...args); + }, remaining); + } + }; +} +var fs4, path5, FileLock, Mutex, MemoryMonitor; +var init_utils = __esm({ + "src/utils.ts"() { + "use strict"; + fs4 = __toESM(require("fs")); + path5 = __toESM(require("path")); + FileLock = class _FileLock { + lockPath; + held = false; + /** Locks older than this are considered stale regardless of PID status */ + static STALE_TIMEOUT_MS = 2 * 60 * 1e3; + // 2 minutes + constructor(lockPath) { + this.lockPath = lockPath; + } + /** + * Acquire the lock. Throws if the lock is held by another live process. + */ + acquire() { + if (fs4.existsSync(this.lockPath)) { + try { + const content = fs4.readFileSync(this.lockPath, "utf-8").trim(); + const pid = parseInt(content, 10); + const stat2 = fs4.statSync(this.lockPath); + const lockAge = Date.now() - stat2.mtimeMs; + if (lockAge < _FileLock.STALE_TIMEOUT_MS && !isNaN(pid) && this.isProcessAlive(pid)) { + throw new Error( + `CodeGraph database is locked by another process (PID ${pid}). If this is stale, run 'codegraph unlock' or delete ${this.lockPath}` + ); + } + fs4.unlinkSync(this.lockPath); + } catch (err) { + if (err instanceof Error && err.message.includes("locked by another")) { + throw err; + } + try { + fs4.unlinkSync(this.lockPath); + } catch { + } + } + } + try { + fs4.writeFileSync(this.lockPath, String(process.pid), { flag: "wx" }); + this.held = true; + } catch (err) { + if (err.code === "EEXIST") { + throw new Error( + `CodeGraph database is locked by another process. If this is stale, run 'codegraph unlock' or delete ${this.lockPath}` + ); + } + throw err; + } + } + /** + * Release the lock + */ + release() { + if (!this.held) return; + try { + const content = fs4.readFileSync(this.lockPath, "utf-8").trim(); + if (parseInt(content, 10) === process.pid) { + fs4.unlinkSync(this.lockPath); + } + } catch { + } + this.held = false; + } + /** + * Execute a function while holding the lock + */ + withLock(fn) { + this.acquire(); + try { + return fn(); + } finally { + this.release(); + } + } + /** + * Execute an async function while holding the lock + */ + async withLockAsync(fn) { + this.acquire(); + try { + return await fn(); + } finally { + this.release(); + } + } + /** + * Check if a process is still running + */ + isProcessAlive(pid) { + try { + process.kill(pid, 0); + return true; + } catch { + return false; + } + } + }; + Mutex = class { + locked = false; + waitQueue = []; + /** + * Acquire the lock + * + * @returns A release function to call when done + */ + async acquire() { + while (this.locked) { + await new Promise((resolve10) => { + this.waitQueue.push(resolve10); + }); + } + this.locked = true; + return () => { + this.locked = false; + const next = this.waitQueue.shift(); + if (next) { + next(); + } + }; + } + /** + * Execute a function while holding the lock + */ + async withLock(fn) { + const release = await this.acquire(); + try { + return await fn(); + } finally { + release(); + } + } + /** + * Check if the lock is currently held + */ + isLocked() { + return this.locked; + } + }; + MemoryMonitor = class { + checkInterval = null; + peakUsage = 0; + threshold; + onThresholdExceeded; + constructor(thresholdMB = 500, onThresholdExceeded) { + this.threshold = thresholdMB * 1024 * 1024; + this.onThresholdExceeded = onThresholdExceeded; + } + /** + * Start monitoring memory usage + */ + start(intervalMs = 1e3) { + this.stop(); + this.peakUsage = 0; + this.checkInterval = setInterval(() => { + const usage = process.memoryUsage().heapUsed; + if (usage > this.peakUsage) { + this.peakUsage = usage; + } + if (usage > this.threshold && this.onThresholdExceeded) { + this.onThresholdExceeded(usage); + } + }, intervalMs); + } + /** + * Stop monitoring + */ + stop() { + if (this.checkInterval) { + clearInterval(this.checkInterval); + this.checkInterval = null; + } + } + /** + * Get peak memory usage in bytes + */ + getPeakUsage() { + return this.peakUsage; + } + /** + * Get current memory usage in bytes + */ + getCurrentUsage() { + return process.memoryUsage().heapUsed; + } + }; + } +}); + +// src/search/query-utils.ts +function getStemVariants(term) { + const variants = /* @__PURE__ */ new Set(); + const t = term.toLowerCase(); + if (t.endsWith("ing") && t.length > 5) { + const base = t.slice(0, -3); + variants.add(base); + variants.add(base + "e"); + if (base.length >= 2 && base[base.length - 1] === base[base.length - 2]) { + variants.add(base.slice(0, -1)); + } + } + if ((t.endsWith("tion") || t.endsWith("sion")) && t.length > 5) { + variants.add(t.slice(0, -3)); + } + if (t.endsWith("ment") && t.length > 6) { + variants.add(t.slice(0, -4)); + } + if (t.endsWith("ies") && t.length > 4) { + variants.add(t.slice(0, -3) + "y"); + } else if (t.endsWith("es") && t.length > 4) { + variants.add(t.slice(0, -2)); + } else if (t.endsWith("s") && !t.endsWith("ss") && t.length > 4) { + variants.add(t.slice(0, -1)); + } + if (t.endsWith("ed") && !t.endsWith("eed") && t.length > 4) { + variants.add(t.slice(0, -1)); + variants.add(t.slice(0, -2)); + if (t.endsWith("ied") && t.length > 5) { + variants.add(t.slice(0, -3) + "y"); + } + } + if (t.endsWith("er") && t.length > 4) { + const base = t.slice(0, -2); + variants.add(base); + variants.add(base + "e"); + if (base.length >= 2 && base[base.length - 1] === base[base.length - 2]) { + variants.add(base.slice(0, -1)); + } + } + return [...variants].filter((v) => v.length >= 3 && v !== t); +} +function extractSearchTerms(query, options) { + const includeStems = options?.stems !== false; + const tokens = /* @__PURE__ */ new Set(); + const compoundPattern = /\b([a-zA-Z][a-zA-Z0-9]*(?:[A-Z][a-z]+)+|[A-Z][a-z]+(?:[A-Z][a-z]*)+)\b/g; + let match; + while ((match = compoundPattern.exec(query)) !== null) { + if (match[1] && match[1].length >= 3) { + tokens.add(match[1].toLowerCase()); + } + } + const snakePattern = /\b([a-zA-Z][a-zA-Z0-9]*(?:_[a-zA-Z0-9]+)+)\b/g; + while ((match = snakePattern.exec(query)) !== null) { + if (match[1] && match[1].length >= 3) { + tokens.add(match[1].toLowerCase()); + } + } + const camelSplit = query.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2"); + const normalised = camelSplit.replace(/[_.]+/g, " "); + const words = normalised.split(/[^a-zA-Z0-9]+/).filter(Boolean); + for (const word of words) { + const lower = word.toLowerCase(); + if (lower.length < 3) continue; + if (STOP_WORDS.has(lower)) continue; + tokens.add(lower); + } + if (includeStems) { + const stems = /* @__PURE__ */ new Set(); + for (const token of tokens) { + for (const variant of getStemVariants(token)) { + if (!tokens.has(variant) && !STOP_WORDS.has(variant)) { + stems.add(variant); + } + } + } + for (const stem of stems) { + tokens.add(stem); + } + } + return [...tokens]; +} +function scorePathRelevance(filePath, query) { + const terms = extractSearchTerms(query, { stems: false }); + if (terms.length === 0) return 0; + const pathLower = filePath.toLowerCase(); + const fileName = path6.basename(filePath).toLowerCase(); + const dirName = path6.dirname(filePath).toLowerCase(); + let score = 0; + for (const term of terms) { + if (fileName.includes(term)) score += 10; + if (dirName.includes(term)) score += 5; + else if (pathLower.includes(term)) score += 3; + } + const queryLower = query.toLowerCase(); + const isTestQuery = queryLower.includes("test") || queryLower.includes("spec"); + if (!isTestQuery && isTestFile(filePath)) { + score -= 15; + } + return score; +} +function isTestFile(filePath) { + const lower = filePath.toLowerCase(); + const fileName = path6.basename(lower); + return fileName.startsWith("test_") || fileName.startsWith("test.") || fileName.endsWith(".test.ts") || fileName.endsWith(".test.js") || fileName.endsWith(".test.tsx") || fileName.endsWith(".test.jsx") || fileName.endsWith(".spec.ts") || fileName.endsWith(".spec.js") || fileName.endsWith("_test.go") || fileName.endsWith("_test.py") || fileName.endsWith("_test.rs") || fileName.endsWith("Tests.java") || fileName.endsWith("Test.java") || fileName.endsWith("Tester.java") || fileName.endsWith("TestCase.java") || lower.includes("/tests/") || lower.includes("/test/") || lower.includes("/__tests__/") || lower.includes("/spec/") || lower.includes("/testlib/") || lower.includes("/testing/") || // Non-production directories: examples, samples, benchmarks, fixtures, demos. + // Check both mid-path (/integration/) and start-of-path (integration/) since + // file paths may be stored as relative paths without a leading slash. + matchesNonProductionDir(lower); +} +function matchesNonProductionDir(lowerPath) { + const dirs = [ + "integration", + "sample", + "samples", + "example", + "examples", + "fixture", + "fixtures", + "benchmark", + "benchmarks", + "demo", + "demos" + ]; + for (const dir of dirs) { + if (lowerPath.includes("/" + dir + "/") || lowerPath.startsWith(dir + "/")) { + return true; + } + } + return false; +} +function nameMatchBonus(nodeName, query) { + const nameLower = nodeName.toLowerCase(); + const rawTerms = query.replace(/([a-z])([A-Z])/g, "$1 $2").split(/[\s_.\-]+/).map((t) => t.toLowerCase()).filter((t) => t.length >= 2); + const queryTokens = query.split(/\s+/).map((t) => t.toLowerCase()).filter((t) => t.length >= 2); + const queryLower = query.replace(/[\s]+/g, "").toLowerCase(); + if (nameLower === queryLower) return 80; + if (queryTokens.length > 1 && queryTokens.includes(nameLower)) return 60; + if (nameLower.startsWith(queryLower)) { + const ratio = queryLower.length / nameLower.length; + return Math.round(10 + 30 * ratio); + } + if (rawTerms.length > 1) { + const allMatch = rawTerms.every((t) => nameLower.includes(t)); + if (allMatch) return 15; + } + if (nameLower.includes(queryLower)) return 10; + return 0; +} +function kindBonus(kind) { + const bonuses = { + function: 10, + method: 10, + class: 8, + interface: 9, + type_alias: 6, + struct: 6, + trait: 9, + enum: 5, + component: 8, + route: 9, + module: 4, + property: 3, + field: 3, + variable: 2, + constant: 3, + import: 1, + export: 1, + parameter: 0, + namespace: 4, + file: 0, + protocol: 9, + enum_member: 3 + }; + return bonuses[kind] ?? 0; +} +var path6, STOP_WORDS; +var init_query_utils = __esm({ + "src/search/query-utils.ts"() { + "use strict"; + path6 = __toESM(require("path")); + STOP_WORDS = /* @__PURE__ */ new Set([ + // English + "the", + "a", + "an", + "and", + "or", + "but", + "in", + "on", + "at", + "to", + "for", + "of", + "with", + "by", + "from", + "is", + "it", + "that", + "this", + "are", + "was", + "be", + "has", + "had", + "have", + "do", + "does", + "did", + "will", + "would", + "could", + "should", + "may", + "might", + "can", + "shall", + "not", + "no", + "all", + "each", + "every", + "how", + "what", + "where", + "when", + "who", + "which", + "why", + "i", + "me", + "my", + "we", + "our", + "you", + "your", + "he", + "she", + "they", + "show", + "give", + "tell", + "been", + "done", + "made", + "used", + "using", + "work", + "works", + "found", + "also", + "into", + "then", + "than", + "just", + "more", + "some", + "such", + "over", + "only", + "out", + "its", + "so", + "up", + "as", + "if", + "look", + "need", + "needs", + "want", + "happen", + "happens", + "affect", + "affected", + "break", + "breaks", + "failing", + "implemented", + "implement", + // Code-specific noise (avoid filtering common symbol names like get/set/add/build/find/list) + "code", + "file", + "files", + "function", + "method", + "class", + "type", + "fix", + "bug", + "called" + ]); + } +}); + +// src/types.ts +var NODE_KINDS, LANGUAGES, DEFAULT_CONFIG; +var init_types = __esm({ + "src/types.ts"() { + "use strict"; + NODE_KINDS = [ + "file", + "module", + "class", + "struct", + "interface", + "trait", + "protocol", + "function", + "method", + "property", + "field", + "variable", + "constant", + "enum", + "enum_member", + "type_alias", + "namespace", + "parameter", + "import", + "export", + "route", + "component" + ]; + LANGUAGES = [ + "typescript", + "javascript", + "tsx", + "jsx", + "python", + "go", + "rust", + "java", + "c", + "cpp", + "csharp", + "php", + "ruby", + "swift", + "kotlin", + "dart", + "svelte", + "vue", + "liquid", + "pascal", + "scala", + "unknown" + ]; + DEFAULT_CONFIG = { + version: 1, + rootDir: ".", + include: [ + // TypeScript/JavaScript + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.jsx", + // Python + "**/*.py", + // Go + "**/*.go", + // Rust + "**/*.rs", + // Java + "**/*.java", + // C/C++ + "**/*.c", + "**/*.h", + "**/*.cpp", + "**/*.hpp", + "**/*.cc", + "**/*.cxx", + // C# + "**/*.cs", + // PHP + "**/*.php", + // Ruby + "**/*.rb", + // Swift + "**/*.swift", + // Kotlin + "**/*.kt", + "**/*.kts", + // Dart + "**/*.dart", + // Svelte + "**/*.svelte", + // Vue + "**/*.vue", + // Liquid (Shopify themes) + "**/*.liquid", + // Pascal / Delphi + "**/*.pas", + "**/*.dpr", + "**/*.dpk", + "**/*.lpr", + "**/*.dfm", + "**/*.fmx", + // Scala + "**/*.scala", + "**/*.sc" + ], + exclude: [ + // Version control + "**/.git/**", + // Dependencies + "**/node_modules/**", + "**/vendor/**", + "**/Pods/**", + // Generic build outputs + "**/dist/**", + "**/build/**", + "**/out/**", + "**/bin/**", + "**/obj/**", + "**/target/**", + // JavaScript/TypeScript + "**/*.min.js", + "**/*.bundle.js", + "**/.next/**", + "**/.nuxt/**", + "**/.svelte-kit/**", + "**/.output/**", + "**/.turbo/**", + "**/.cache/**", + "**/.parcel-cache/**", + "**/.vite/**", + "**/.astro/**", + "**/.docusaurus/**", + "**/.gatsby/**", + "**/.webpack/**", + "**/.nx/**", + "**/.yarn/cache/**", + "**/.pnpm-store/**", + "**/storybook-static/**", + // React Native / Expo + "**/.expo/**", + "**/web-build/**", + "**/ios/Pods/**", + "**/ios/build/**", + "**/android/build/**", + "**/android/.gradle/**", + // Python + "**/__pycache__/**", + "**/.venv/**", + "**/venv/**", + "**/site-packages/**", + "**/dist-packages/**", + "**/.pytest_cache/**", + "**/.mypy_cache/**", + "**/.ruff_cache/**", + "**/.tox/**", + "**/.nox/**", + "**/*.egg-info/**", + "**/.eggs/**", + // Go + "**/go/pkg/mod/**", + // Rust + "**/target/debug/**", + "**/target/release/**", + // Java/Kotlin/Gradle + "**/.gradle/**", + "**/.m2/**", + "**/generated-sources/**", + "**/.kotlin/**", + // Dart/Flutter + "**/.dart_tool/**", + // C#/.NET + "**/.vs/**", + "**/.nuget/**", + "**/artifacts/**", + "**/publish/**", + // C/C++ + "**/cmake-build-*/**", + "**/CMakeFiles/**", + "**/bazel-*/**", + "**/vcpkg_installed/**", + "**/.conan/**", + "**/Debug/**", + "**/Release/**", + "**/x64/**", + "**/.pio/**", + // Platform.io (IoT/embedded build artifacts and library deps) + // Electron + "**/release/**", + "**/*.app/**", + "**/*.asar", + // Swift/iOS/Xcode + "**/DerivedData/**", + "**/.build/**", + "**/.swiftpm/**", + "**/xcuserdata/**", + "**/Carthage/Build/**", + "**/SourcePackages/**", + // Delphi/Pascal + "**/__history/**", + "**/__recovery/**", + "**/*.dcu", + // PHP + "**/.composer/**", + "**/storage/framework/**", + "**/bootstrap/cache/**", + // Ruby + "**/.bundle/**", + "**/tmp/cache/**", + "**/public/assets/**", + "**/public/packs/**", + "**/.yardoc/**", + // Testing/Coverage + "**/coverage/**", + "**/htmlcov/**", + "**/.nyc_output/**", + "**/test-results/**", + "**/.coverage/**", + // IDE/Editor + "**/.idea/**", + // Logs and temp + "**/logs/**", + "**/tmp/**", + "**/temp/**", + // Documentation build output + "**/_build/**", + "**/docs/_build/**", + "**/site/**" + ], + languages: [], + frameworks: [], + maxFileSize: 1024 * 1024, + // 1MB + extractDocstrings: true, + trackCallSites: true + }; + } +}); + +// src/search/query-parser.ts +function unquote(s) { + if (s.length >= 2 && s.startsWith('"') && s.endsWith('"')) return s.slice(1, -1); + return s; +} +function parseQuery(raw) { + const out = { + text: "", + kinds: [], + languages: [], + pathFilters: [], + nameFilters: [] + }; + const tokens = []; + let i = 0; + while (i < raw.length) { + while (i < raw.length && /\s/.test(raw[i])) i++; + if (i >= raw.length) break; + const start = i; + while (i < raw.length && !/\s/.test(raw[i])) { + if (raw[i] === '"') { + const end = raw.indexOf('"', i + 1); + if (end === -1) { + i = raw.length; + break; + } + i = end + 1; + continue; + } + i++; + } + tokens.push(raw.slice(start, i)); + } + const textParts = []; + for (const tok of tokens) { + const colon = tok.indexOf(":"); + if (colon <= 0 || colon === tok.length - 1) { + textParts.push(tok); + continue; + } + const key = tok.slice(0, colon).toLowerCase(); + const valueRaw = unquote(tok.slice(colon + 1)); + if (!valueRaw) { + textParts.push(tok); + continue; + } + switch (key) { + case "kind": { + if (KIND_VALUES.has(valueRaw)) { + out.kinds.push(valueRaw); + } else { + textParts.push(tok); + } + break; + } + case "lang": + case "language": { + const lower = valueRaw.toLowerCase(); + if (LANGUAGE_VALUES.has(lower)) { + out.languages.push(lower); + } else { + textParts.push(tok); + } + break; + } + case "path": + out.pathFilters.push(valueRaw); + break; + case "name": + out.nameFilters.push(valueRaw); + break; + default: + textParts.push(tok); + } + } + out.text = textParts.join(" ").trim(); + return out; +} +function boundedEditDistance(a, b, maxDist) { + if (a === b) return 0; + const al = a.length; + const bl = b.length; + if (Math.abs(al - bl) > maxDist) return maxDist + 1; + if (al === 0) return bl; + if (bl === 0) return al; + let prev = new Array(bl + 1); + let cur = new Array(bl + 1); + for (let j = 0; j <= bl; j++) prev[j] = j; + for (let i = 1; i <= al; i++) { + cur[0] = i; + let rowMin = cur[0]; + for (let j = 1; j <= bl; j++) { + const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1; + const insertion = cur[j - 1] + 1; + const deletion = prev[j] + 1; + const substitution = prev[j - 1] + cost; + cur[j] = Math.min(insertion, deletion, substitution); + if (cur[j] < rowMin) rowMin = cur[j]; + } + if (rowMin > maxDist) return maxDist + 1; + [prev, cur] = [cur, prev]; + } + return prev[bl]; +} +var KIND_VALUES, LANGUAGE_VALUES; +var init_query_parser = __esm({ + "src/search/query-parser.ts"() { + "use strict"; + init_types(); + KIND_VALUES = new Set(NODE_KINDS); + LANGUAGE_VALUES = new Set(LANGUAGES); + } +}); + +// src/db/queries.ts +function rowToNode(row) { + return { + id: row.id, + kind: row.kind, + name: row.name, + qualifiedName: row.qualified_name, + filePath: row.file_path, + language: row.language, + startLine: row.start_line, + endLine: row.end_line, + startColumn: row.start_column, + endColumn: row.end_column, + docstring: row.docstring ?? void 0, + signature: row.signature ?? void 0, + visibility: row.visibility, + isExported: row.is_exported === 1, + isAsync: row.is_async === 1, + isStatic: row.is_static === 1, + isAbstract: row.is_abstract === 1, + decorators: row.decorators ? safeJsonParse(row.decorators, void 0) : void 0, + typeParameters: row.type_parameters ? safeJsonParse(row.type_parameters, void 0) : void 0, + updatedAt: row.updated_at + }; +} +function rowToEdge(row) { + return { + source: row.source, + target: row.target, + kind: row.kind, + metadata: row.metadata ? safeJsonParse(row.metadata, void 0) : void 0, + line: row.line ?? void 0, + column: row.col ?? void 0, + provenance: row.provenance + }; +} +function rowToFileRecord(row) { + return { + path: row.path, + contentHash: row.content_hash, + language: row.language, + size: row.size, + modifiedAt: row.modified_at, + indexedAt: row.indexed_at, + nodeCount: row.node_count, + errors: row.errors ? safeJsonParse(row.errors, void 0) : void 0 + }; +} +var QueryBuilder; +var init_queries = __esm({ + "src/db/queries.ts"() { + "use strict"; + init_utils(); + init_query_utils(); + init_query_parser(); + QueryBuilder = class { + db; + // Node cache for frequently accessed nodes (LRU-style, max 1000 entries) + nodeCache = /* @__PURE__ */ new Map(); + maxCacheSize = 1e3; + // Prepared statements (lazily initialized) + stmts = {}; + constructor(db) { + this.db = db; + } + // =========================================================================== + // Node Operations + // =========================================================================== + /** + * Insert a new node + */ + insertNode(node) { + if (!this.stmts.insertNode) { + this.stmts.insertNode = this.db.prepare(` + INSERT OR REPLACE INTO nodes ( + id, kind, name, qualified_name, file_path, language, + start_line, end_line, start_column, end_column, + docstring, signature, visibility, + is_exported, is_async, is_static, is_abstract, + decorators, type_parameters, updated_at + ) VALUES ( + @id, @kind, @name, @qualifiedName, @filePath, @language, + @startLine, @endLine, @startColumn, @endColumn, + @docstring, @signature, @visibility, + @isExported, @isAsync, @isStatic, @isAbstract, + @decorators, @typeParameters, @updatedAt + ) + `); + } + if (!node.id || !node.kind || !node.name || !node.filePath || !node.language) { + console.error("[CodeGraph] Skipping node with missing required fields:", { + id: node.id, + kind: node.kind, + name: node.name, + filePath: node.filePath, + language: node.language + }); + return; + } + try { + this.stmts.insertNode.run({ + id: node.id, + kind: node.kind, + name: node.name, + qualifiedName: node.qualifiedName ?? node.name, + filePath: node.filePath, + language: node.language, + startLine: node.startLine ?? 0, + endLine: node.endLine ?? 0, + startColumn: node.startColumn ?? 0, + endColumn: node.endColumn ?? 0, + docstring: node.docstring ?? null, + signature: node.signature ?? null, + visibility: node.visibility ?? null, + isExported: node.isExported ? 1 : 0, + isAsync: node.isAsync ? 1 : 0, + isStatic: node.isStatic ? 1 : 0, + isAbstract: node.isAbstract ? 1 : 0, + decorators: node.decorators ? JSON.stringify(node.decorators) : null, + typeParameters: node.typeParameters ? JSON.stringify(node.typeParameters) : null, + updatedAt: node.updatedAt ?? Date.now() + }); + } catch (error) { + throw error; + } + } + /** + * Insert multiple nodes in a transaction + */ + insertNodes(nodes) { + this.db.transaction(() => { + for (const node of nodes) { + this.insertNode(node); + } + })(); + } + /** + * Update an existing node + */ + updateNode(node) { + if (!this.stmts.updateNode) { + this.stmts.updateNode = this.db.prepare(` + UPDATE nodes SET + kind = @kind, + name = @name, + qualified_name = @qualifiedName, + file_path = @filePath, + language = @language, + start_line = @startLine, + end_line = @endLine, + start_column = @startColumn, + end_column = @endColumn, + docstring = @docstring, + signature = @signature, + visibility = @visibility, + is_exported = @isExported, + is_async = @isAsync, + is_static = @isStatic, + is_abstract = @isAbstract, + decorators = @decorators, + type_parameters = @typeParameters, + updated_at = @updatedAt + WHERE id = @id + `); + } + this.nodeCache.delete(node.id); + if (!node.id || !node.kind || !node.name || !node.filePath || !node.language) { + console.error("[CodeGraph] Skipping node update with missing required fields:", node.id); + return; + } + this.stmts.updateNode.run({ + id: node.id, + kind: node.kind, + name: node.name, + qualifiedName: node.qualifiedName ?? node.name, + filePath: node.filePath, + language: node.language, + startLine: node.startLine ?? 0, + endLine: node.endLine ?? 0, + startColumn: node.startColumn ?? 0, + endColumn: node.endColumn ?? 0, + docstring: node.docstring ?? null, + signature: node.signature ?? null, + visibility: node.visibility ?? null, + isExported: node.isExported ? 1 : 0, + isAsync: node.isAsync ? 1 : 0, + isStatic: node.isStatic ? 1 : 0, + isAbstract: node.isAbstract ? 1 : 0, + decorators: node.decorators ? JSON.stringify(node.decorators) : null, + typeParameters: node.typeParameters ? JSON.stringify(node.typeParameters) : null, + updatedAt: node.updatedAt ?? Date.now() + }); + } + /** + * Delete a node by ID + */ + deleteNode(id) { + if (!this.stmts.deleteNode) { + this.stmts.deleteNode = this.db.prepare("DELETE FROM nodes WHERE id = ?"); + } + this.nodeCache.delete(id); + this.stmts.deleteNode.run(id); + } + /** + * Delete all nodes for a file + */ + deleteNodesByFile(filePath) { + if (!this.stmts.deleteNodesByFile) { + this.stmts.deleteNodesByFile = this.db.prepare("DELETE FROM nodes WHERE file_path = ?"); + } + for (const [id, node] of this.nodeCache) { + if (node.filePath === filePath) { + this.nodeCache.delete(id); + } + } + this.stmts.deleteNodesByFile.run(filePath); + } + /** + * Get a node by ID + */ + getNodeById(id) { + if (this.nodeCache.has(id)) { + const cached = this.nodeCache.get(id); + this.nodeCache.delete(id); + this.nodeCache.set(id, cached); + return cached; + } + if (!this.stmts.getNodeById) { + this.stmts.getNodeById = this.db.prepare("SELECT * FROM nodes WHERE id = ?"); + } + const row = this.stmts.getNodeById.get(id); + if (!row) { + return null; + } + const node = rowToNode(row); + this.cacheNode(node); + return node; + } + /** + * Add a node to the cache, evicting oldest if needed + */ + cacheNode(node) { + if (this.nodeCache.size >= this.maxCacheSize) { + const firstKey = this.nodeCache.keys().next().value; + if (firstKey) { + this.nodeCache.delete(firstKey); + } + } + this.nodeCache.set(node.id, node); + } + /** + * Clear the node cache + */ + clearCache() { + this.nodeCache.clear(); + } + /** + * Get all nodes in a file + */ + getNodesByFile(filePath) { + if (!this.stmts.getNodesByFile) { + this.stmts.getNodesByFile = this.db.prepare( + "SELECT * FROM nodes WHERE file_path = ? ORDER BY start_line" + ); + } + const rows = this.stmts.getNodesByFile.all(filePath); + return rows.map(rowToNode); + } + /** + * Get all nodes of a specific kind + */ + getNodesByKind(kind) { + if (!this.stmts.getNodesByKind) { + this.stmts.getNodesByKind = this.db.prepare("SELECT * FROM nodes WHERE kind = ?"); + } + const rows = this.stmts.getNodesByKind.all(kind); + return rows.map(rowToNode); + } + /** + * Get all nodes in the database + */ + getAllNodes() { + const rows = this.db.prepare("SELECT * FROM nodes").all(); + return rows.map(rowToNode); + } + /** + * Get nodes by exact name match (uses idx_nodes_name index) + */ + getNodesByName(name) { + if (!this.stmts.getNodesByName) { + this.stmts.getNodesByName = this.db.prepare("SELECT * FROM nodes WHERE name = ?"); + } + const rows = this.stmts.getNodesByName.all(name); + return rows.map(rowToNode); + } + /** + * Get nodes by exact qualified name match (uses idx_nodes_qualified_name index) + */ + getNodesByQualifiedNameExact(qualifiedName) { + if (!this.stmts.getNodesByQualifiedNameExact) { + this.stmts.getNodesByQualifiedNameExact = this.db.prepare( + "SELECT * FROM nodes WHERE qualified_name = ?" + ); + } + const rows = this.stmts.getNodesByQualifiedNameExact.all(qualifiedName); + return rows.map(rowToNode); + } + /** + * Get nodes by lowercase name match (uses idx_nodes_lower_name expression index) + */ + getNodesByLowerName(lowerName) { + if (!this.stmts.getNodesByLowerName) { + this.stmts.getNodesByLowerName = this.db.prepare( + "SELECT * FROM nodes WHERE lower(name) = ?" + ); + } + const rows = this.stmts.getNodesByLowerName.all(lowerName); + return rows.map(rowToNode); + } + /** + * Search nodes by name using FTS with fallback to LIKE for better matching + * + * Search strategy: + * 1. Try FTS5 prefix match (query*) for word-start matching + * 2. If no results, try LIKE for substring matching (e.g., "signIn" finds "signInWithGoogle") + * 3. Score results based on match quality + */ + searchNodes(query, options = {}) { + const { limit = 100, offset = 0 } = options; + const parsed = parseQuery(query); + const mergedKinds = parsed.kinds.length > 0 ? Array.from(/* @__PURE__ */ new Set([...options.kinds ?? [], ...parsed.kinds])) : options.kinds; + const mergedLanguages = parsed.languages.length > 0 ? Array.from(/* @__PURE__ */ new Set([...options.languages ?? [], ...parsed.languages])) : options.languages; + const pathFilters = parsed.pathFilters; + const nameFilters = parsed.nameFilters; + const text = parsed.text; + const kinds = mergedKinds; + const languages = mergedLanguages; + let results = text ? this.searchNodesFTS(text, { kinds, languages, limit, offset }) : this.searchAllByFilters({ kinds, languages, limit: limit * 5 }); + if (results.length === 0 && text.length >= 2) { + results = this.searchNodesLike(text, { kinds, languages, limit, offset }); + } + if (results.length === 0 && text.length >= 3) { + results = this.searchNodesFuzzy(text, { kinds, languages, limit }); + } + if (results.length > 0 && query) { + const existingIds = new Set(results.map((r) => r.node.id)); + const maxFtsScore = Math.max(...results.map((r) => r.score)); + const terms = query.split(/\s+/).filter((t) => t.length >= 2); + for (const term of terms) { + let sql = "SELECT * FROM nodes WHERE name = ? COLLATE NOCASE"; + const params = [term]; + if (kinds && kinds.length > 0) { + sql += ` AND kind IN (${kinds.map(() => "?").join(",")})`; + params.push(...kinds); + } + if (languages && languages.length > 0) { + sql += ` AND language IN (${languages.map(() => "?").join(",")})`; + params.push(...languages); + } + sql += " LIMIT 20"; + const rows = this.db.prepare(sql).all(...params); + for (const row of rows) { + if (!existingIds.has(row.id)) { + results.push({ node: rowToNode(row), score: maxFtsScore }); + existingIds.add(row.id); + } + } + } + } + if (results.length > 0 && (text || query)) { + const scoringQuery = text || query; + results = results.map((r) => ({ + ...r, + score: r.score + kindBonus(r.node.kind) + scorePathRelevance(r.node.filePath, scoringQuery) + nameMatchBonus(r.node.name, scoringQuery) + })); + results.sort((a, b) => b.score - a.score); + if (results.length > limit) { + results = results.slice(0, limit); + } + } + if (pathFilters.length > 0) { + const lowered = pathFilters.map((p) => p.toLowerCase()); + results = results.filter((r) => { + const fp = r.node.filePath.toLowerCase(); + return lowered.some((p) => fp.includes(p)); + }); + } + if (nameFilters.length > 0) { + const lowered = nameFilters.map((n) => n.toLowerCase()); + results = results.filter((r) => { + const nm = r.node.name.toLowerCase(); + return lowered.some((n) => nm.includes(n)); + }); + } + return results; + } + /** + * Match-everything path used when the user supplied only field + * filters (`kind:function lang:typescript`) with no text. Returns + * candidates ordered by name; the caller's filter pass narrows to + * what was asked for. + */ + searchAllByFilters(options) { + const { kinds, languages, limit } = options; + let sql = "SELECT * FROM nodes WHERE 1=1"; + const params = []; + if (kinds && kinds.length > 0) { + sql += ` AND kind IN (${kinds.map(() => "?").join(",")})`; + params.push(...kinds); + } + if (languages && languages.length > 0) { + sql += ` AND language IN (${languages.map(() => "?").join(",")})`; + params.push(...languages); + } + sql += " ORDER BY name LIMIT ?"; + params.push(limit); + const rows = this.db.prepare(sql).all(...params); + return rows.map((row) => ({ node: rowToNode(row), score: 1 })); + } + /** + * Fuzzy fallback: when zero FTS/LIKE hits, try an edit-distance + * sweep over the distinct symbol-name set. Caps `maxDist` at 2 so + * `getUssr` finds `getUser` but `process` doesn't match `prosody`. + * Bounded edit distance keeps each comparison cheap; the per-query + * scan is O(distinct-name-count) which is far smaller than total + * node count on any real codebase. + */ + searchNodesFuzzy(text, options) { + const { kinds, languages, limit } = options; + const lowered = text.toLowerCase(); + const maxDist = lowered.length <= 4 ? 1 : 2; + const allNames = this.getAllNodeNames(); + const candidates = []; + for (const name of allNames) { + const dist = boundedEditDistance(name.toLowerCase(), lowered, maxDist); + if (dist <= maxDist) candidates.push({ name, dist }); + } + candidates.sort((a, b) => a.dist - b.dist); + const FUZZY_FOLLOWUP_CAP = Math.max(limit * 2, 50); + const cappedCandidates = candidates.slice(0, FUZZY_FOLLOWUP_CAP); + const results = []; + const seen = /* @__PURE__ */ new Set(); + for (const c of cappedCandidates) { + if (results.length >= limit) break; + let sql = "SELECT * FROM nodes WHERE name = ?"; + const params = [c.name]; + if (kinds && kinds.length > 0) { + sql += ` AND kind IN (${kinds.map(() => "?").join(",")})`; + params.push(...kinds); + } + if (languages && languages.length > 0) { + sql += ` AND language IN (${languages.map(() => "?").join(",")})`; + params.push(...languages); + } + sql += " LIMIT 5"; + const rows = this.db.prepare(sql).all(...params); + for (const row of rows) { + if (seen.has(row.id)) continue; + seen.add(row.id); + results.push({ node: rowToNode(row), score: 1 / (1 + c.dist) }); + if (results.length >= limit) break; + } + } + return results; + } + /** + * FTS5 search with prefix matching + */ + searchNodesFTS(query, options) { + const { kinds, languages, limit = 100, offset = 0 } = options; + const ftsQuery = query.replace(/['"*():^]/g, "").split(/\s+/).filter((term) => term.length > 0).filter((term) => !/^(AND|OR|NOT|NEAR)$/i.test(term)).map((term) => `"${term}"*`).join(" OR "); + if (!ftsQuery) { + return []; + } + const ftsLimit = Math.max(limit * 5, 100); + let sql = ` + SELECT nodes.*, bm25(nodes_fts, 0, 20, 5, 1, 2) as score + FROM nodes_fts + JOIN nodes ON nodes_fts.id = nodes.id + WHERE nodes_fts MATCH ? + `; + const params = [ftsQuery]; + if (kinds && kinds.length > 0) { + sql += ` AND nodes.kind IN (${kinds.map(() => "?").join(",")})`; + params.push(...kinds); + } + if (languages && languages.length > 0) { + sql += ` AND nodes.language IN (${languages.map(() => "?").join(",")})`; + params.push(...languages); + } + sql += " ORDER BY score LIMIT ? OFFSET ?"; + params.push(ftsLimit, offset); + try { + const rows = this.db.prepare(sql).all(...params); + return rows.map((row) => ({ + node: rowToNode(row), + score: Math.abs(row.score) + // bm25 returns negative scores + })); + } catch { + return []; + } + } + /** + * LIKE-based substring search for cases where FTS doesn't match + * Useful for camelCase matching (e.g., "signIn" finds "signInWithGoogle") + */ + searchNodesLike(query, options) { + const { kinds, languages, limit = 100, offset = 0 } = options; + let sql = ` + SELECT nodes.*, + CASE + WHEN name = ? THEN 1.0 + WHEN name LIKE ? THEN 0.9 + WHEN name LIKE ? THEN 0.8 + WHEN qualified_name LIKE ? THEN 0.7 + ELSE 0.5 + END as score + FROM nodes + WHERE ( + name LIKE ? OR + qualified_name LIKE ? OR + name LIKE ? + ) + `; + const exactMatch = query; + const startsWith = `${query}%`; + const contains = `%${query}%`; + const params = [ + exactMatch, + // Exact match score + startsWith, + // Starts with score + contains, + // Contains score + contains, + // Qualified name score + contains, + // WHERE: name contains + contains, + // WHERE: qualified_name contains + startsWith + // WHERE: name starts with + ]; + if (kinds && kinds.length > 0) { + sql += ` AND kind IN (${kinds.map(() => "?").join(",")})`; + params.push(...kinds); + } + if (languages && languages.length > 0) { + sql += ` AND language IN (${languages.map(() => "?").join(",")})`; + params.push(...languages); + } + sql += " ORDER BY score DESC, length(name) ASC LIMIT ? OFFSET ?"; + params.push(limit, offset); + const rows = this.db.prepare(sql).all(...params); + return rows.map((row) => ({ + node: rowToNode(row), + score: row.score + })); + } + /** + * Find nodes by exact name match + * + * Used for hybrid search - looks up symbols by exact name or case-insensitive match. + * Returns high-confidence matches for known symbol names extracted from query. + * + * @param names - Array of symbol names to look up + * @param options - Search options (kinds, languages, limit) + * @returns SearchResult array with exact matches scored at 1.0 + */ + findNodesByExactName(names, options = {}) { + if (names.length === 0) return []; + const { kinds, languages, limit = 50 } = options; + const nameToFiles = /* @__PURE__ */ new Map(); + for (const name of names) { + let sql = "SELECT DISTINCT file_path FROM nodes WHERE name COLLATE NOCASE = ?"; + const params = [name]; + if (kinds && kinds.length > 0) { + sql += ` AND kind IN (${kinds.map(() => "?").join(",")})`; + params.push(...kinds); + } + sql += " LIMIT 100"; + const rows = this.db.prepare(sql).all(...params); + nameToFiles.set(name.toLowerCase(), new Set(rows.map((r) => r.file_path))); + } + const distinctiveFiles = /* @__PURE__ */ new Set(); + for (const [, files] of nameToFiles) { + if (files.size > 0 && files.size < 10) { + for (const f of files) distinctiveFiles.add(f); + } + } + const perNameLimit = Math.max(8, Math.ceil(limit / names.length)); + const allResults = []; + const seenIds = /* @__PURE__ */ new Set(); + for (const name of names) { + let sql = ` + SELECT nodes.*, 1.0 as score + FROM nodes + WHERE name COLLATE NOCASE = ? + `; + const params = [name]; + if (kinds && kinds.length > 0) { + sql += ` AND kind IN (${kinds.map(() => "?").join(",")})`; + params.push(...kinds); + } + if (languages && languages.length > 0) { + sql += ` AND language IN (${languages.map(() => "?").join(",")})`; + params.push(...languages); + } + sql += " LIMIT ?"; + params.push(Math.max(perNameLimit * 3, 50)); + const rows = this.db.prepare(sql).all(...params); + const nameResults = []; + for (const row of rows) { + const node = rowToNode(row); + if (seenIds.has(node.id)) continue; + const coLocationBoost = distinctiveFiles.has(node.filePath) ? 20 : 0; + nameResults.push({ node, score: row.score + coLocationBoost }); + } + nameResults.sort((a, b) => b.score - a.score); + for (const r of nameResults.slice(0, perNameLimit)) { + seenIds.add(r.node.id); + allResults.push(r); + } + } + allResults.sort((a, b) => b.score - a.score); + return allResults.slice(0, limit); + } + /** + * Find nodes whose name contains a substring (LIKE-based). + * Useful for CamelCase-part matching where FTS fails because + * e.g. "TransportSearchAction" is one FTS token, not matchable by "Search"*. + * + * Results are ordered by name length (shorter = more likely to be the core type). + */ + findNodesByNameSubstring(substring, options = {}) { + const { kinds, languages, limit = 30, excludePrefix } = options; + let sql = ` + SELECT nodes.*, 1.0 as score + FROM nodes + WHERE name LIKE ? + `; + const params = [`%${substring}%`]; + if (excludePrefix) { + sql += ` AND name NOT LIKE ?`; + params.push(`${substring}%`); + } + if (kinds && kinds.length > 0) { + sql += ` AND kind IN (${kinds.map(() => "?").join(",")})`; + params.push(...kinds); + } + if (languages && languages.length > 0) { + sql += ` AND language IN (${languages.map(() => "?").join(",")})`; + params.push(...languages); + } + sql += " ORDER BY length(name) ASC LIMIT ?"; + params.push(limit); + const rows = this.db.prepare(sql).all(...params); + return rows.map((row) => ({ + node: rowToNode(row), + score: row.score + })); + } + // =========================================================================== + // Edge Operations + // =========================================================================== + /** + * Insert a new edge + */ + insertEdge(edge) { + if (!this.stmts.insertEdge) { + this.stmts.insertEdge = this.db.prepare(` + INSERT OR IGNORE INTO edges (source, target, kind, metadata, line, col, provenance) + VALUES (@source, @target, @kind, @metadata, @line, @col, @provenance) + `); + } + this.stmts.insertEdge.run({ + source: edge.source, + target: edge.target, + kind: edge.kind, + metadata: edge.metadata ? JSON.stringify(edge.metadata) : null, + line: edge.line ?? null, + col: edge.column ?? null, + provenance: edge.provenance ?? null + }); + } + /** + * Insert multiple edges in a transaction + */ + insertEdges(edges) { + this.db.transaction(() => { + for (const edge of edges) { + this.insertEdge(edge); + } + })(); + } + /** + * Delete all edges from a source node + */ + deleteEdgesBySource(sourceId) { + if (!this.stmts.deleteEdgesBySource) { + this.stmts.deleteEdgesBySource = this.db.prepare("DELETE FROM edges WHERE source = ?"); + } + this.stmts.deleteEdgesBySource.run(sourceId); + } + /** + * Get outgoing edges from a node + */ + getOutgoingEdges(sourceId, kinds, provenance) { + if (kinds && kinds.length > 0 || provenance) { + let sql = "SELECT * FROM edges WHERE source = ?"; + const params = [sourceId]; + if (kinds && kinds.length > 0) { + sql += ` AND kind IN (${kinds.map(() => "?").join(",")})`; + params.push(...kinds); + } + if (provenance) { + sql += " AND provenance = ?"; + params.push(provenance); + } + const rows2 = this.db.prepare(sql).all(...params); + return rows2.map(rowToEdge); + } + if (!this.stmts.getEdgesBySource) { + this.stmts.getEdgesBySource = this.db.prepare("SELECT * FROM edges WHERE source = ?"); + } + const rows = this.stmts.getEdgesBySource.all(sourceId); + return rows.map(rowToEdge); + } + /** + * Get incoming edges to a node + */ + getIncomingEdges(targetId, kinds) { + if (kinds && kinds.length > 0) { + const sql = `SELECT * FROM edges WHERE target = ? AND kind IN (${kinds.map(() => "?").join(",")})`; + const rows2 = this.db.prepare(sql).all(targetId, ...kinds); + return rows2.map(rowToEdge); + } + if (!this.stmts.getEdgesByTarget) { + this.stmts.getEdgesByTarget = this.db.prepare("SELECT * FROM edges WHERE target = ?"); + } + const rows = this.stmts.getEdgesByTarget.all(targetId); + return rows.map(rowToEdge); + } + /** + * Find all edges where both source and target are in the given node set. + * Useful for recovering inter-node connectivity after BFS. + */ + findEdgesBetweenNodes(nodeIds, kinds) { + if (nodeIds.length === 0) return []; + const idsJson = JSON.stringify(nodeIds); + let sql = `SELECT * FROM edges WHERE source IN (SELECT value FROM json_each(?)) AND target IN (SELECT value FROM json_each(?))`; + const params = [idsJson, idsJson]; + if (kinds && kinds.length > 0) { + sql += ` AND kind IN (${kinds.map(() => "?").join(",")})`; + params.push(...kinds); + } + const rows = this.db.prepare(sql).all(...params); + return rows.map(rowToEdge); + } + // =========================================================================== + // File Operations + // =========================================================================== + /** + * Insert or update a file record + */ + upsertFile(file) { + if (!this.stmts.upsertFile) { + this.stmts.upsertFile = this.db.prepare(` + INSERT INTO files (path, content_hash, language, size, modified_at, indexed_at, node_count, errors) + VALUES (@path, @contentHash, @language, @size, @modifiedAt, @indexedAt, @nodeCount, @errors) + ON CONFLICT(path) DO UPDATE SET + content_hash = @contentHash, + language = @language, + size = @size, + modified_at = @modifiedAt, + indexed_at = @indexedAt, + node_count = @nodeCount, + errors = @errors + `); + } + this.stmts.upsertFile.run({ + path: file.path, + contentHash: file.contentHash, + language: file.language, + size: file.size, + modifiedAt: file.modifiedAt, + indexedAt: file.indexedAt, + nodeCount: file.nodeCount, + errors: file.errors ? JSON.stringify(file.errors) : null + }); + } + /** + * Delete a file record and its nodes + */ + deleteFile(filePath) { + this.db.transaction(() => { + this.deleteNodesByFile(filePath); + if (!this.stmts.deleteFile) { + this.stmts.deleteFile = this.db.prepare("DELETE FROM files WHERE path = ?"); + } + this.stmts.deleteFile.run(filePath); + })(); + } + /** + * Get a file record by path + */ + getFileByPath(filePath) { + if (!this.stmts.getFileByPath) { + this.stmts.getFileByPath = this.db.prepare("SELECT * FROM files WHERE path = ?"); + } + const row = this.stmts.getFileByPath.get(filePath); + return row ? rowToFileRecord(row) : null; + } + /** + * Get all tracked files + */ + getAllFiles() { + if (!this.stmts.getAllFiles) { + this.stmts.getAllFiles = this.db.prepare("SELECT * FROM files ORDER BY path"); + } + const rows = this.stmts.getAllFiles.all(); + return rows.map(rowToFileRecord); + } + /** + * Get files that need re-indexing (hash changed) + */ + getStaleFiles(currentHashes) { + const files = this.getAllFiles(); + return files.filter((f) => { + const currentHash = currentHashes.get(f.path); + return currentHash && currentHash !== f.contentHash; + }); + } + // =========================================================================== + // Unresolved References + // =========================================================================== + /** + * Insert an unresolved reference + */ + insertUnresolvedRef(ref) { + if (!this.stmts.insertUnresolved) { + this.stmts.insertUnresolved = this.db.prepare(` + INSERT INTO unresolved_refs (from_node_id, reference_name, reference_kind, line, col, candidates, file_path, language) + VALUES (@fromNodeId, @referenceName, @referenceKind, @line, @col, @candidates, @filePath, @language) + `); + } + this.stmts.insertUnresolved.run({ + fromNodeId: ref.fromNodeId, + referenceName: ref.referenceName, + referenceKind: ref.referenceKind, + line: ref.line, + col: ref.column, + candidates: ref.candidates ? JSON.stringify(ref.candidates) : null, + filePath: ref.filePath ?? "", + language: ref.language ?? "unknown" + }); + } + /** + * Insert multiple unresolved references in a transaction + */ + insertUnresolvedRefsBatch(refs) { + if (refs.length === 0) return; + const insert = this.db.transaction(() => { + for (const ref of refs) { + this.insertUnresolvedRef(ref); + } + }); + insert(); + } + /** + * Delete unresolved references from a node + */ + deleteUnresolvedByNode(nodeId) { + if (!this.stmts.deleteUnresolvedByNode) { + this.stmts.deleteUnresolvedByNode = this.db.prepare( + "DELETE FROM unresolved_refs WHERE from_node_id = ?" + ); + } + this.stmts.deleteUnresolvedByNode.run(nodeId); + } + /** + * Get unresolved references by name (for resolution) + */ + getUnresolvedByName(name) { + if (!this.stmts.getUnresolvedByName) { + this.stmts.getUnresolvedByName = this.db.prepare( + "SELECT * FROM unresolved_refs WHERE reference_name = ?" + ); + } + const rows = this.stmts.getUnresolvedByName.all(name); + return rows.map((row) => ({ + fromNodeId: row.from_node_id, + referenceName: row.reference_name, + referenceKind: row.reference_kind, + line: row.line, + column: row.col, + candidates: row.candidates ? safeJsonParse(row.candidates, void 0) : void 0, + filePath: row.file_path, + language: row.language + })); + } + /** + * Get all unresolved references + */ + getUnresolvedReferences() { + const rows = this.db.prepare("SELECT * FROM unresolved_refs").all(); + return rows.map((row) => ({ + fromNodeId: row.from_node_id, + referenceName: row.reference_name, + referenceKind: row.reference_kind, + line: row.line, + column: row.col, + candidates: row.candidates ? safeJsonParse(row.candidates, void 0) : void 0, + filePath: row.file_path, + language: row.language + })); + } + /** + * Get the count of unresolved references without loading them into memory + */ + getUnresolvedReferencesCount() { + if (!this.stmts.getUnresolvedCount) { + this.stmts.getUnresolvedCount = this.db.prepare( + "SELECT COUNT(*) as count FROM unresolved_refs" + ); + } + const row = this.stmts.getUnresolvedCount.get(); + return row.count; + } + /** + * Get a batch of unresolved references using LIMIT/OFFSET pagination. + * Used to process references in bounded memory chunks. + */ + getUnresolvedReferencesBatch(offset, limit) { + if (!this.stmts.getUnresolvedBatch) { + this.stmts.getUnresolvedBatch = this.db.prepare( + "SELECT * FROM unresolved_refs LIMIT ? OFFSET ?" + ); + } + const rows = this.stmts.getUnresolvedBatch.all(limit, offset); + return rows.map((row) => ({ + fromNodeId: row.from_node_id, + referenceName: row.reference_name, + referenceKind: row.reference_kind, + line: row.line, + column: row.col, + candidates: row.candidates ? safeJsonParse(row.candidates, void 0) : void 0, + filePath: row.file_path, + language: row.language + })); + } + /** + * Get all tracked file paths (lightweight — no full FileRecord objects) + */ + getAllFilePaths() { + if (!this.stmts.getAllFilePaths) { + this.stmts.getAllFilePaths = this.db.prepare("SELECT path FROM files ORDER BY path"); + } + const rows = this.stmts.getAllFilePaths.all(); + return rows.map((r) => r.path); + } + /** + * Get all distinct node names (lightweight — just name strings for pre-filtering) + */ + getAllNodeNames() { + if (!this.stmts.getAllNodeNames) { + this.stmts.getAllNodeNames = this.db.prepare("SELECT DISTINCT name FROM nodes"); + } + const rows = this.stmts.getAllNodeNames.all(); + return rows.map((r) => r.name); + } + /** + * Get unresolved references scoped to specific file paths. + * Uses the idx_unresolved_file_path index for efficient lookup. + */ + getUnresolvedReferencesByFiles(filePaths) { + if (filePaths.length === 0) return []; + const placeholders = filePaths.map(() => "?").join(","); + const rows = this.db.prepare(`SELECT * FROM unresolved_refs WHERE file_path IN (${placeholders})`).all(...filePaths); + return rows.map((row) => ({ + fromNodeId: row.from_node_id, + referenceName: row.reference_name, + referenceKind: row.reference_kind, + line: row.line, + column: row.col, + candidates: row.candidates ? safeJsonParse(row.candidates, void 0) : void 0, + filePath: row.file_path, + language: row.language + })); + } + /** + * Delete all unresolved references (after resolution) + */ + clearUnresolvedReferences() { + this.db.exec("DELETE FROM unresolved_refs"); + } + /** + * Delete resolved references by their IDs + */ + deleteResolvedReferences(fromNodeIds) { + if (fromNodeIds.length === 0) return; + const placeholders = fromNodeIds.map(() => "?").join(","); + this.db.prepare(`DELETE FROM unresolved_refs WHERE from_node_id IN (${placeholders})`).run(...fromNodeIds); + } + /** + * Delete specific resolved references by (fromNodeId, referenceName, referenceKind) tuples. + * More precise than deleteResolvedReferences — only removes refs that were actually resolved. + */ + deleteSpecificResolvedReferences(refs) { + if (refs.length === 0) return; + const stmt = this.db.prepare( + "DELETE FROM unresolved_refs WHERE from_node_id = ? AND reference_name = ? AND reference_kind = ?" + ); + const deleteMany = this.db.transaction((items) => { + for (const ref of items) { + stmt.run(ref.fromNodeId, ref.referenceName, ref.referenceKind); + } + }); + deleteMany(refs); + } + // =========================================================================== + // Statistics + // =========================================================================== + /** + * Get graph statistics + */ + getStats() { + const counts = this.db.prepare(` + SELECT + (SELECT COUNT(*) FROM nodes) AS node_count, + (SELECT COUNT(*) FROM edges) AS edge_count, + (SELECT COUNT(*) FROM files) AS file_count + `).get(); + const nodesByKind = {}; + const nodeKindRows = this.db.prepare("SELECT kind, COUNT(*) as count FROM nodes GROUP BY kind").all(); + for (const row of nodeKindRows) { + nodesByKind[row.kind] = row.count; + } + const edgesByKind = {}; + const edgeKindRows = this.db.prepare("SELECT kind, COUNT(*) as count FROM edges GROUP BY kind").all(); + for (const row of edgeKindRows) { + edgesByKind[row.kind] = row.count; + } + const filesByLanguage = {}; + const languageRows = this.db.prepare("SELECT language, COUNT(*) as count FROM files GROUP BY language").all(); + for (const row of languageRows) { + filesByLanguage[row.language] = row.count; + } + return { + nodeCount: counts.node_count, + edgeCount: counts.edge_count, + fileCount: counts.file_count, + nodesByKind, + edgesByKind, + filesByLanguage, + dbSizeBytes: 0, + // Set by caller using DatabaseConnection.getSize() + lastUpdated: Date.now() + }; + } + // =========================================================================== + // Project Metadata + // =========================================================================== + /** + * Get a metadata value by key + */ + getMetadata(key) { + const row = this.db.prepare("SELECT value FROM project_metadata WHERE key = ?").get(key); + return row?.value ?? null; + } + /** + * Set a metadata key-value pair (upsert) + */ + setMetadata(key, value) { + this.db.prepare( + "INSERT INTO project_metadata (key, value, updated_at) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at" + ).run(key, value, Date.now()); + } + /** + * Get all metadata as a key-value record + */ + getAllMetadata() { + const rows = this.db.prepare("SELECT key, value FROM project_metadata").all(); + const result = {}; + for (const row of rows) { + result[row.key] = row.value; + } + return result; + } + /** + * Clear all data from the database + */ + clear() { + this.nodeCache.clear(); + this.db.transaction(() => { + this.db.exec("DELETE FROM unresolved_refs"); + this.db.exec("DELETE FROM edges"); + this.db.exec("DELETE FROM nodes"); + this.db.exec("DELETE FROM files"); + })(); + } + }; + } +}); + +// node_modules/picomatch/lib/constants.js +var require_constants = __commonJS({ + "node_modules/picomatch/lib/constants.js"(exports2, module2) { + "use strict"; + var WIN_SLASH = "\\\\/"; + var WIN_NO_SLASH = `[^${WIN_SLASH}]`; + var DEFAULT_MAX_EXTGLOB_RECURSION = 0; + var DOT_LITERAL = "\\."; + var PLUS_LITERAL = "\\+"; + var QMARK_LITERAL = "\\?"; + var SLASH_LITERAL = "\\/"; + var ONE_CHAR = "(?=.)"; + var QMARK = "[^/]"; + var END_ANCHOR = `(?:${SLASH_LITERAL}|$)`; + var START_ANCHOR = `(?:^|${SLASH_LITERAL})`; + var DOTS_SLASH = `${DOT_LITERAL}{1,2}${END_ANCHOR}`; + var NO_DOT = `(?!${DOT_LITERAL})`; + var NO_DOTS = `(?!${START_ANCHOR}${DOTS_SLASH})`; + var NO_DOT_SLASH = `(?!${DOT_LITERAL}{0,1}${END_ANCHOR})`; + var NO_DOTS_SLASH = `(?!${DOTS_SLASH})`; + var QMARK_NO_DOT = `[^.${SLASH_LITERAL}]`; + var STAR = `${QMARK}*?`; + var SEP = "/"; + var POSIX_CHARS = { + DOT_LITERAL, + PLUS_LITERAL, + QMARK_LITERAL, + SLASH_LITERAL, + ONE_CHAR, + QMARK, + END_ANCHOR, + DOTS_SLASH, + NO_DOT, + NO_DOTS, + NO_DOT_SLASH, + NO_DOTS_SLASH, + QMARK_NO_DOT, + STAR, + START_ANCHOR, + SEP + }; + var WINDOWS_CHARS = { + ...POSIX_CHARS, + SLASH_LITERAL: `[${WIN_SLASH}]`, + QMARK: WIN_NO_SLASH, + STAR: `${WIN_NO_SLASH}*?`, + DOTS_SLASH: `${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$)`, + NO_DOT: `(?!${DOT_LITERAL})`, + NO_DOTS: `(?!(?:^|[${WIN_SLASH}])${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$))`, + NO_DOT_SLASH: `(?!${DOT_LITERAL}{0,1}(?:[${WIN_SLASH}]|$))`, + NO_DOTS_SLASH: `(?!${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$))`, + QMARK_NO_DOT: `[^.${WIN_SLASH}]`, + START_ANCHOR: `(?:^|[${WIN_SLASH}])`, + END_ANCHOR: `(?:[${WIN_SLASH}]|$)`, + SEP: "\\" + }; + var POSIX_REGEX_SOURCE = { + __proto__: null, + alnum: "a-zA-Z0-9", + alpha: "a-zA-Z", + ascii: "\\x00-\\x7F", + blank: " \\t", + cntrl: "\\x00-\\x1F\\x7F", + digit: "0-9", + graph: "\\x21-\\x7E", + lower: "a-z", + print: "\\x20-\\x7E ", + punct: "\\-!\"#$%&'()\\*+,./:;<=>?@[\\]^_`{|}~", + space: " \\t\\r\\n\\v\\f", + upper: "A-Z", + word: "A-Za-z0-9_", + xdigit: "A-Fa-f0-9" + }; + module2.exports = { + DEFAULT_MAX_EXTGLOB_RECURSION, + MAX_LENGTH: 1024 * 64, + POSIX_REGEX_SOURCE, + // regular expressions + REGEX_BACKSLASH: /\\(?![*+?^${}(|)[\]])/g, + REGEX_NON_SPECIAL_CHARS: /^[^@![\].,$*+?^{}()|\\/]+/, + REGEX_SPECIAL_CHARS: /[-*+?.^${}(|)[\]]/, + REGEX_SPECIAL_CHARS_BACKREF: /(\\?)((\W)(\3*))/g, + REGEX_SPECIAL_CHARS_GLOBAL: /([-*+?.^${}(|)[\]])/g, + REGEX_REMOVE_BACKSLASH: /(?:\[.*?[^\\]\]|\\(?=.))/g, + // Replace globs with equivalent patterns to reduce parsing time. + REPLACEMENTS: { + __proto__: null, + "***": "*", + "**/**": "**", + "**/**/**": "**" + }, + // Digits + CHAR_0: 48, + /* 0 */ + CHAR_9: 57, + /* 9 */ + // Alphabet chars. + CHAR_UPPERCASE_A: 65, + /* A */ + CHAR_LOWERCASE_A: 97, + /* a */ + CHAR_UPPERCASE_Z: 90, + /* Z */ + CHAR_LOWERCASE_Z: 122, + /* z */ + CHAR_LEFT_PARENTHESES: 40, + /* ( */ + CHAR_RIGHT_PARENTHESES: 41, + /* ) */ + CHAR_ASTERISK: 42, + /* * */ + // Non-alphabetic chars. + CHAR_AMPERSAND: 38, + /* & */ + CHAR_AT: 64, + /* @ */ + CHAR_BACKWARD_SLASH: 92, + /* \ */ + CHAR_CARRIAGE_RETURN: 13, + /* \r */ + CHAR_CIRCUMFLEX_ACCENT: 94, + /* ^ */ + CHAR_COLON: 58, + /* : */ + CHAR_COMMA: 44, + /* , */ + CHAR_DOT: 46, + /* . */ + CHAR_DOUBLE_QUOTE: 34, + /* " */ + CHAR_EQUAL: 61, + /* = */ + CHAR_EXCLAMATION_MARK: 33, + /* ! */ + CHAR_FORM_FEED: 12, + /* \f */ + CHAR_FORWARD_SLASH: 47, + /* / */ + CHAR_GRAVE_ACCENT: 96, + /* ` */ + CHAR_HASH: 35, + /* # */ + CHAR_HYPHEN_MINUS: 45, + /* - */ + CHAR_LEFT_ANGLE_BRACKET: 60, + /* < */ + CHAR_LEFT_CURLY_BRACE: 123, + /* { */ + CHAR_LEFT_SQUARE_BRACKET: 91, + /* [ */ + CHAR_LINE_FEED: 10, + /* \n */ + CHAR_NO_BREAK_SPACE: 160, + /* \u00A0 */ + CHAR_PERCENT: 37, + /* % */ + CHAR_PLUS: 43, + /* + */ + CHAR_QUESTION_MARK: 63, + /* ? */ + CHAR_RIGHT_ANGLE_BRACKET: 62, + /* > */ + CHAR_RIGHT_CURLY_BRACE: 125, + /* } */ + CHAR_RIGHT_SQUARE_BRACKET: 93, + /* ] */ + CHAR_SEMICOLON: 59, + /* ; */ + CHAR_SINGLE_QUOTE: 39, + /* ' */ + CHAR_SPACE: 32, + /* */ + CHAR_TAB: 9, + /* \t */ + CHAR_UNDERSCORE: 95, + /* _ */ + CHAR_VERTICAL_LINE: 124, + /* | */ + CHAR_ZERO_WIDTH_NOBREAK_SPACE: 65279, + /* \uFEFF */ + /** + * Create EXTGLOB_CHARS + */ + extglobChars(chars) { + return { + "!": { type: "negate", open: "(?:(?!(?:", close: `))${chars.STAR})` }, + "?": { type: "qmark", open: "(?:", close: ")?" }, + "+": { type: "plus", open: "(?:", close: ")+" }, + "*": { type: "star", open: "(?:", close: ")*" }, + "@": { type: "at", open: "(?:", close: ")" } + }; + }, + /** + * Create GLOB_CHARS + */ + globChars(win32) { + return win32 === true ? WINDOWS_CHARS : POSIX_CHARS; + } + }; + } +}); + +// node_modules/picomatch/lib/utils.js +var require_utils = __commonJS({ + "node_modules/picomatch/lib/utils.js"(exports2) { + "use strict"; + var { + REGEX_BACKSLASH, + REGEX_REMOVE_BACKSLASH, + REGEX_SPECIAL_CHARS, + REGEX_SPECIAL_CHARS_GLOBAL + } = require_constants(); + exports2.isObject = (val) => val !== null && typeof val === "object" && !Array.isArray(val); + exports2.hasRegexChars = (str) => REGEX_SPECIAL_CHARS.test(str); + exports2.isRegexChar = (str) => str.length === 1 && exports2.hasRegexChars(str); + exports2.escapeRegex = (str) => str.replace(REGEX_SPECIAL_CHARS_GLOBAL, "\\$1"); + exports2.toPosixSlashes = (str) => str.replace(REGEX_BACKSLASH, "/"); + exports2.isWindows = () => { + if (typeof navigator !== "undefined" && navigator.platform) { + const platform = navigator.platform.toLowerCase(); + return platform === "win32" || platform === "windows"; + } + if (typeof process !== "undefined" && process.platform) { + return process.platform === "win32"; + } + return false; + }; + exports2.removeBackslashes = (str) => { + return str.replace(REGEX_REMOVE_BACKSLASH, (match) => { + return match === "\\" ? "" : match; + }); + }; + exports2.escapeLast = (input, char, lastIdx) => { + const idx = input.lastIndexOf(char, lastIdx); + if (idx === -1) return input; + if (input[idx - 1] === "\\") return exports2.escapeLast(input, char, idx - 1); + return `${input.slice(0, idx)}\\${input.slice(idx)}`; + }; + exports2.removePrefix = (input, state = {}) => { + let output = input; + if (output.startsWith("./")) { + output = output.slice(2); + state.prefix = "./"; + } + return output; + }; + exports2.wrapOutput = (input, state = {}, options = {}) => { + const prepend = options.contains ? "" : "^"; + const append = options.contains ? "" : "$"; + let output = `${prepend}(?:${input})${append}`; + if (state.negated === true) { + output = `(?:^(?!${output}).*$)`; + } + return output; + }; + exports2.basename = (path20, { windows } = {}) => { + const segs = path20.split(windows ? /[\\/]/ : "/"); + const last = segs[segs.length - 1]; + if (last === "") { + return segs[segs.length - 2]; + } + return last; + }; + } +}); + +// node_modules/picomatch/lib/scan.js +var require_scan = __commonJS({ + "node_modules/picomatch/lib/scan.js"(exports2, module2) { + "use strict"; + var utils = require_utils(); + var { + CHAR_ASTERISK, + /* * */ + CHAR_AT, + /* @ */ + CHAR_BACKWARD_SLASH, + /* \ */ + CHAR_COMMA, + /* , */ + CHAR_DOT, + /* . */ + CHAR_EXCLAMATION_MARK, + /* ! */ + CHAR_FORWARD_SLASH, + /* / */ + CHAR_LEFT_CURLY_BRACE, + /* { */ + CHAR_LEFT_PARENTHESES, + /* ( */ + CHAR_LEFT_SQUARE_BRACKET, + /* [ */ + CHAR_PLUS, + /* + */ + CHAR_QUESTION_MARK, + /* ? */ + CHAR_RIGHT_CURLY_BRACE, + /* } */ + CHAR_RIGHT_PARENTHESES, + /* ) */ + CHAR_RIGHT_SQUARE_BRACKET + /* ] */ + } = require_constants(); + var isPathSeparator = (code) => { + return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH; + }; + var depth = (token) => { + if (token.isPrefix !== true) { + token.depth = token.isGlobstar ? Infinity : 1; + } + }; + var scan = (input, options) => { + const opts = options || {}; + const length = input.length - 1; + const scanToEnd = opts.parts === true || opts.scanToEnd === true; + const slashes = []; + const tokens = []; + const parts = []; + let str = input; + let index = -1; + let start = 0; + let lastIndex = 0; + let isBrace = false; + let isBracket = false; + let isGlob = false; + let isExtglob = false; + let isGlobstar = false; + let braceEscaped = false; + let backslashes = false; + let negated = false; + let negatedExtglob = false; + let finished = false; + let braces = 0; + let prev; + let code; + let token = { value: "", depth: 0, isGlob: false }; + const eos = () => index >= length; + const peek = () => str.charCodeAt(index + 1); + const advance = () => { + prev = code; + return str.charCodeAt(++index); + }; + while (index < length) { + code = advance(); + let next; + if (code === CHAR_BACKWARD_SLASH) { + backslashes = token.backslashes = true; + code = advance(); + if (code === CHAR_LEFT_CURLY_BRACE) { + braceEscaped = true; + } + continue; + } + if (braceEscaped === true || code === CHAR_LEFT_CURLY_BRACE) { + braces++; + while (eos() !== true && (code = advance())) { + if (code === CHAR_BACKWARD_SLASH) { + backslashes = token.backslashes = true; + advance(); + continue; + } + if (code === CHAR_LEFT_CURLY_BRACE) { + braces++; + continue; + } + if (braceEscaped !== true && code === CHAR_DOT && (code = advance()) === CHAR_DOT) { + isBrace = token.isBrace = true; + isGlob = token.isGlob = true; + finished = true; + if (scanToEnd === true) { + continue; + } + break; + } + if (braceEscaped !== true && code === CHAR_COMMA) { + isBrace = token.isBrace = true; + isGlob = token.isGlob = true; + finished = true; + if (scanToEnd === true) { + continue; + } + break; + } + if (code === CHAR_RIGHT_CURLY_BRACE) { + braces--; + if (braces === 0) { + braceEscaped = false; + isBrace = token.isBrace = true; + finished = true; + break; + } + } + } + if (scanToEnd === true) { + continue; + } + break; + } + if (code === CHAR_FORWARD_SLASH) { + slashes.push(index); + tokens.push(token); + token = { value: "", depth: 0, isGlob: false }; + if (finished === true) continue; + if (prev === CHAR_DOT && index === start + 1) { + start += 2; + continue; + } + lastIndex = index + 1; + continue; + } + if (opts.noext !== true) { + const isExtglobChar = code === CHAR_PLUS || code === CHAR_AT || code === CHAR_ASTERISK || code === CHAR_QUESTION_MARK || code === CHAR_EXCLAMATION_MARK; + if (isExtglobChar === true && peek() === CHAR_LEFT_PARENTHESES) { + isGlob = token.isGlob = true; + isExtglob = token.isExtglob = true; + finished = true; + if (code === CHAR_EXCLAMATION_MARK && index === start) { + negatedExtglob = true; + } + if (scanToEnd === true) { + while (eos() !== true && (code = advance())) { + if (code === CHAR_BACKWARD_SLASH) { + backslashes = token.backslashes = true; + code = advance(); + continue; + } + if (code === CHAR_RIGHT_PARENTHESES) { + isGlob = token.isGlob = true; + finished = true; + break; + } + } + continue; + } + break; + } + } + if (code === CHAR_ASTERISK) { + if (prev === CHAR_ASTERISK) isGlobstar = token.isGlobstar = true; + isGlob = token.isGlob = true; + finished = true; + if (scanToEnd === true) { + continue; + } + break; + } + if (code === CHAR_QUESTION_MARK) { + isGlob = token.isGlob = true; + finished = true; + if (scanToEnd === true) { + continue; + } + break; + } + if (code === CHAR_LEFT_SQUARE_BRACKET) { + while (eos() !== true && (next = advance())) { + if (next === CHAR_BACKWARD_SLASH) { + backslashes = token.backslashes = true; + advance(); + continue; + } + if (next === CHAR_RIGHT_SQUARE_BRACKET) { + isBracket = token.isBracket = true; + isGlob = token.isGlob = true; + finished = true; + break; + } + } + if (scanToEnd === true) { + continue; + } + break; + } + if (opts.nonegate !== true && code === CHAR_EXCLAMATION_MARK && index === start) { + negated = token.negated = true; + start++; + continue; + } + if (opts.noparen !== true && code === CHAR_LEFT_PARENTHESES) { + isGlob = token.isGlob = true; + if (scanToEnd === true) { + while (eos() !== true && (code = advance())) { + if (code === CHAR_LEFT_PARENTHESES) { + backslashes = token.backslashes = true; + code = advance(); + continue; + } + if (code === CHAR_RIGHT_PARENTHESES) { + finished = true; + break; + } + } + continue; + } + break; + } + if (isGlob === true) { + finished = true; + if (scanToEnd === true) { + continue; + } + break; + } + } + if (opts.noext === true) { + isExtglob = false; + isGlob = false; + } + let base = str; + let prefix = ""; + let glob = ""; + if (start > 0) { + prefix = str.slice(0, start); + str = str.slice(start); + lastIndex -= start; + } + if (base && isGlob === true && lastIndex > 0) { + base = str.slice(0, lastIndex); + glob = str.slice(lastIndex); + } else if (isGlob === true) { + base = ""; + glob = str; + } else { + base = str; + } + if (base && base !== "" && base !== "/" && base !== str) { + if (isPathSeparator(base.charCodeAt(base.length - 1))) { + base = base.slice(0, -1); + } + } + if (opts.unescape === true) { + if (glob) glob = utils.removeBackslashes(glob); + if (base && backslashes === true) { + base = utils.removeBackslashes(base); + } + } + const state = { + prefix, + input, + start, + base, + glob, + isBrace, + isBracket, + isGlob, + isExtglob, + isGlobstar, + negated, + negatedExtglob + }; + if (opts.tokens === true) { + state.maxDepth = 0; + if (!isPathSeparator(code)) { + tokens.push(token); + } + state.tokens = tokens; + } + if (opts.parts === true || opts.tokens === true) { + let prevIndex; + for (let idx = 0; idx < slashes.length; idx++) { + const n = prevIndex ? prevIndex + 1 : start; + const i = slashes[idx]; + const value = input.slice(n, i); + if (opts.tokens) { + if (idx === 0 && start !== 0) { + tokens[idx].isPrefix = true; + tokens[idx].value = prefix; + } else { + tokens[idx].value = value; + } + depth(tokens[idx]); + state.maxDepth += tokens[idx].depth; + } + if (idx !== 0 || value !== "") { + parts.push(value); + } + prevIndex = i; + } + if (prevIndex && prevIndex + 1 < input.length) { + const value = input.slice(prevIndex + 1); + parts.push(value); + if (opts.tokens) { + tokens[tokens.length - 1].value = value; + depth(tokens[tokens.length - 1]); + state.maxDepth += tokens[tokens.length - 1].depth; + } + } + state.slashes = slashes; + state.parts = parts; + } + return state; + }; + module2.exports = scan; + } +}); + +// node_modules/picomatch/lib/parse.js +var require_parse = __commonJS({ + "node_modules/picomatch/lib/parse.js"(exports2, module2) { + "use strict"; + var constants = require_constants(); + var utils = require_utils(); + var { + MAX_LENGTH, + POSIX_REGEX_SOURCE, + REGEX_NON_SPECIAL_CHARS, + REGEX_SPECIAL_CHARS_BACKREF, + REPLACEMENTS + } = constants; + var expandRange = (args, options) => { + if (typeof options.expandRange === "function") { + return options.expandRange(...args, options); + } + args.sort(); + const value = `[${args.join("-")}]`; + try { + new RegExp(value); + } catch (ex) { + return args.map((v) => utils.escapeRegex(v)).join(".."); + } + return value; + }; + var syntaxError = (type, char) => { + return `Missing ${type}: "${char}" - use "\\\\${char}" to match literal characters`; + }; + var splitTopLevel = (input) => { + const parts = []; + let bracket = 0; + let paren = 0; + let quote = 0; + let value = ""; + let escaped = false; + for (const ch of input) { + if (escaped === true) { + value += ch; + escaped = false; + continue; + } + if (ch === "\\") { + value += ch; + escaped = true; + continue; + } + if (ch === '"') { + quote = quote === 1 ? 0 : 1; + value += ch; + continue; + } + if (quote === 0) { + if (ch === "[") { + bracket++; + } else if (ch === "]" && bracket > 0) { + bracket--; + } else if (bracket === 0) { + if (ch === "(") { + paren++; + } else if (ch === ")" && paren > 0) { + paren--; + } else if (ch === "|" && paren === 0) { + parts.push(value); + value = ""; + continue; + } + } + } + value += ch; + } + parts.push(value); + return parts; + }; + var isPlainBranch = (branch) => { + let escaped = false; + for (const ch of branch) { + if (escaped === true) { + escaped = false; + continue; + } + if (ch === "\\") { + escaped = true; + continue; + } + if (/[?*+@!()[\]{}]/.test(ch)) { + return false; + } + } + return true; + }; + var normalizeSimpleBranch = (branch) => { + let value = branch.trim(); + let changed = true; + while (changed === true) { + changed = false; + if (/^@\([^\\()[\]{}|]+\)$/.test(value)) { + value = value.slice(2, -1); + changed = true; + } + } + if (!isPlainBranch(value)) { + return; + } + return value.replace(/\\(.)/g, "$1"); + }; + var hasRepeatedCharPrefixOverlap = (branches) => { + const values = branches.map(normalizeSimpleBranch).filter(Boolean); + for (let i = 0; i < values.length; i++) { + for (let j = i + 1; j < values.length; j++) { + const a = values[i]; + const b = values[j]; + const char = a[0]; + if (!char || a !== char.repeat(a.length) || b !== char.repeat(b.length)) { + continue; + } + if (a === b || a.startsWith(b) || b.startsWith(a)) { + return true; + } + } + } + return false; + }; + var parseRepeatedExtglob = (pattern, requireEnd = true) => { + if (pattern[0] !== "+" && pattern[0] !== "*" || pattern[1] !== "(") { + return; + } + let bracket = 0; + let paren = 0; + let quote = 0; + let escaped = false; + for (let i = 1; i < pattern.length; i++) { + const ch = pattern[i]; + if (escaped === true) { + escaped = false; + continue; + } + if (ch === "\\") { + escaped = true; + continue; + } + if (ch === '"') { + quote = quote === 1 ? 0 : 1; + continue; + } + if (quote === 1) { + continue; + } + if (ch === "[") { + bracket++; + continue; + } + if (ch === "]" && bracket > 0) { + bracket--; + continue; + } + if (bracket > 0) { + continue; + } + if (ch === "(") { + paren++; + continue; + } + if (ch === ")") { + paren--; + if (paren === 0) { + if (requireEnd === true && i !== pattern.length - 1) { + return; + } + return { + type: pattern[0], + body: pattern.slice(2, i), + end: i + }; + } + } + } + }; + var getStarExtglobSequenceOutput = (pattern) => { + let index = 0; + const chars = []; + while (index < pattern.length) { + const match = parseRepeatedExtglob(pattern.slice(index), false); + if (!match || match.type !== "*") { + return; + } + const branches = splitTopLevel(match.body).map((branch2) => branch2.trim()); + if (branches.length !== 1) { + return; + } + const branch = normalizeSimpleBranch(branches[0]); + if (!branch || branch.length !== 1) { + return; + } + chars.push(branch); + index += match.end + 1; + } + if (chars.length < 1) { + return; + } + const source = chars.length === 1 ? utils.escapeRegex(chars[0]) : `[${chars.map((ch) => utils.escapeRegex(ch)).join("")}]`; + return `${source}*`; + }; + var repeatedExtglobRecursion = (pattern) => { + let depth = 0; + let value = pattern.trim(); + let match = parseRepeatedExtglob(value); + while (match) { + depth++; + value = match.body.trim(); + match = parseRepeatedExtglob(value); + } + return depth; + }; + var analyzeRepeatedExtglob = (body, options) => { + if (options.maxExtglobRecursion === false) { + return { risky: false }; + } + const max = typeof options.maxExtglobRecursion === "number" ? options.maxExtglobRecursion : constants.DEFAULT_MAX_EXTGLOB_RECURSION; + const branches = splitTopLevel(body).map((branch) => branch.trim()); + if (branches.length > 1) { + if (branches.some((branch) => branch === "") || branches.some((branch) => /^[*?]+$/.test(branch)) || hasRepeatedCharPrefixOverlap(branches)) { + return { risky: true }; + } + } + for (const branch of branches) { + const safeOutput = getStarExtglobSequenceOutput(branch); + if (safeOutput) { + return { risky: true, safeOutput }; + } + if (repeatedExtglobRecursion(branch) > max) { + return { risky: true }; + } + } + return { risky: false }; + }; + var parse4 = (input, options) => { + if (typeof input !== "string") { + throw new TypeError("Expected a string"); + } + input = REPLACEMENTS[input] || input; + const opts = { ...options }; + const max = typeof opts.maxLength === "number" ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; + let len = input.length; + if (len > max) { + throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`); + } + const bos = { type: "bos", value: "", output: opts.prepend || "" }; + const tokens = [bos]; + const capture = opts.capture ? "" : "?:"; + const PLATFORM_CHARS = constants.globChars(opts.windows); + const EXTGLOB_CHARS = constants.extglobChars(PLATFORM_CHARS); + const { + DOT_LITERAL, + PLUS_LITERAL, + SLASH_LITERAL, + ONE_CHAR, + DOTS_SLASH, + NO_DOT, + NO_DOT_SLASH, + NO_DOTS_SLASH, + QMARK, + QMARK_NO_DOT, + STAR, + START_ANCHOR + } = PLATFORM_CHARS; + const globstar = (opts2) => { + return `(${capture}(?:(?!${START_ANCHOR}${opts2.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`; + }; + const nodot = opts.dot ? "" : NO_DOT; + const qmarkNoDot = opts.dot ? QMARK : QMARK_NO_DOT; + let star = opts.bash === true ? globstar(opts) : STAR; + if (opts.capture) { + star = `(${star})`; + } + if (typeof opts.noext === "boolean") { + opts.noextglob = opts.noext; + } + const state = { + input, + index: -1, + start: 0, + dot: opts.dot === true, + consumed: "", + output: "", + prefix: "", + backtrack: false, + negated: false, + brackets: 0, + braces: 0, + parens: 0, + quotes: 0, + globstar: false, + tokens + }; + input = utils.removePrefix(input, state); + len = input.length; + const extglobs = []; + const braces = []; + const stack = []; + let prev = bos; + let value; + const eos = () => state.index === len - 1; + const peek = state.peek = (n = 1) => input[state.index + n]; + const advance = state.advance = () => input[++state.index] || ""; + const remaining = () => input.slice(state.index + 1); + const consume = (value2 = "", num = 0) => { + state.consumed += value2; + state.index += num; + }; + const append = (token) => { + state.output += token.output != null ? token.output : token.value; + consume(token.value); + }; + const negate = () => { + let count = 1; + while (peek() === "!" && (peek(2) !== "(" || peek(3) === "?")) { + advance(); + state.start++; + count++; + } + if (count % 2 === 0) { + return false; + } + state.negated = true; + state.start++; + return true; + }; + const increment = (type) => { + state[type]++; + stack.push(type); + }; + const decrement = (type) => { + state[type]--; + stack.pop(); + }; + const push = (tok) => { + if (prev.type === "globstar") { + const isBrace = state.braces > 0 && (tok.type === "comma" || tok.type === "brace"); + const isExtglob = tok.extglob === true || extglobs.length && (tok.type === "pipe" || tok.type === "paren"); + if (tok.type !== "slash" && tok.type !== "paren" && !isBrace && !isExtglob) { + state.output = state.output.slice(0, -prev.output.length); + prev.type = "star"; + prev.value = "*"; + prev.output = star; + state.output += prev.output; + } + } + if (extglobs.length && tok.type !== "paren") { + extglobs[extglobs.length - 1].inner += tok.value; + } + if (tok.value || tok.output) append(tok); + if (prev && prev.type === "text" && tok.type === "text") { + prev.output = (prev.output || prev.value) + tok.value; + prev.value += tok.value; + return; + } + tok.prev = prev; + tokens.push(tok); + prev = tok; + }; + const extglobOpen = (type, value2) => { + const token = { ...EXTGLOB_CHARS[value2], conditions: 1, inner: "" }; + token.prev = prev; + token.parens = state.parens; + token.output = state.output; + token.startIndex = state.index; + token.tokensIndex = tokens.length; + const output = (opts.capture ? "(" : "") + token.open; + increment("parens"); + push({ type, value: value2, output: state.output ? "" : ONE_CHAR }); + push({ type: "paren", extglob: true, value: advance(), output }); + extglobs.push(token); + }; + const extglobClose = (token) => { + const literal = input.slice(token.startIndex, state.index + 1); + const body = input.slice(token.startIndex + 2, state.index); + const analysis = analyzeRepeatedExtglob(body, opts); + if ((token.type === "plus" || token.type === "star") && analysis.risky) { + const safeOutput = analysis.safeOutput ? (token.output ? "" : ONE_CHAR) + (opts.capture ? `(${analysis.safeOutput})` : analysis.safeOutput) : void 0; + const open = tokens[token.tokensIndex]; + open.type = "text"; + open.value = literal; + open.output = safeOutput || utils.escapeRegex(literal); + for (let i = token.tokensIndex + 1; i < tokens.length; i++) { + tokens[i].value = ""; + tokens[i].output = ""; + delete tokens[i].suffix; + } + state.output = token.output + open.output; + state.backtrack = true; + push({ type: "paren", extglob: true, value, output: "" }); + decrement("parens"); + return; + } + let output = token.close + (opts.capture ? ")" : ""); + let rest; + if (token.type === "negate") { + let extglobStar = star; + if (token.inner && token.inner.length > 1 && token.inner.includes("/")) { + extglobStar = globstar(opts); + } + if (extglobStar !== star || eos() || /^\)+$/.test(remaining())) { + output = token.close = `)$))${extglobStar}`; + } + if (token.inner.includes("*") && (rest = remaining()) && /^\.[^\\/.]+$/.test(rest)) { + const expression = parse4(rest, { ...options, fastpaths: false }).output; + output = token.close = `)${expression})${extglobStar})`; + } + if (token.prev.type === "bos") { + state.negatedExtglob = true; + } + } + push({ type: "paren", extglob: true, value, output }); + decrement("parens"); + }; + if (opts.fastpaths !== false && !/(^[*!]|[/()[\]{}"])/.test(input)) { + let backslashes = false; + let output = input.replace(REGEX_SPECIAL_CHARS_BACKREF, (m, esc, chars, first, rest, index) => { + if (first === "\\") { + backslashes = true; + return m; + } + if (first === "?") { + if (esc) { + return esc + first + (rest ? QMARK.repeat(rest.length) : ""); + } + if (index === 0) { + return qmarkNoDot + (rest ? QMARK.repeat(rest.length) : ""); + } + return QMARK.repeat(chars.length); + } + if (first === ".") { + return DOT_LITERAL.repeat(chars.length); + } + if (first === "*") { + if (esc) { + return esc + first + (rest ? star : ""); + } + return star; + } + return esc ? m : `\\${m}`; + }); + if (backslashes === true) { + if (opts.unescape === true) { + output = output.replace(/\\/g, ""); + } else { + output = output.replace(/\\+/g, (m) => { + return m.length % 2 === 0 ? "\\\\" : m ? "\\" : ""; + }); + } + } + if (output === input && opts.contains === true) { + state.output = input; + return state; + } + state.output = utils.wrapOutput(output, state, options); + return state; + } + while (!eos()) { + value = advance(); + if (value === "\0") { + continue; + } + if (value === "\\") { + const next = peek(); + if (next === "/" && opts.bash !== true) { + continue; + } + if (next === "." || next === ";") { + continue; + } + if (!next) { + value += "\\"; + push({ type: "text", value }); + continue; + } + const match = /^\\+/.exec(remaining()); + let slashes = 0; + if (match && match[0].length > 2) { + slashes = match[0].length; + state.index += slashes; + if (slashes % 2 !== 0) { + value += "\\"; + } + } + if (opts.unescape === true) { + value = advance(); + } else { + value += advance(); + } + if (state.brackets === 0) { + push({ type: "text", value }); + continue; + } + } + if (state.brackets > 0 && (value !== "]" || prev.value === "[" || prev.value === "[^")) { + if (opts.posix !== false && value === ":") { + const inner = prev.value.slice(1); + if (inner.includes("[")) { + prev.posix = true; + if (inner.includes(":")) { + const idx = prev.value.lastIndexOf("["); + const pre = prev.value.slice(0, idx); + const rest2 = prev.value.slice(idx + 2); + const posix = POSIX_REGEX_SOURCE[rest2]; + if (posix) { + prev.value = pre + posix; + state.backtrack = true; + advance(); + if (!bos.output && tokens.indexOf(prev) === 1) { + bos.output = ONE_CHAR; + } + continue; + } + } + } + } + if (value === "[" && peek() !== ":" || value === "-" && peek() === "]") { + value = `\\${value}`; + } + if (value === "]" && (prev.value === "[" || prev.value === "[^")) { + value = `\\${value}`; + } + if (opts.posix === true && value === "!" && prev.value === "[") { + value = "^"; + } + prev.value += value; + append({ value }); + continue; + } + if (state.quotes === 1 && value !== '"') { + value = utils.escapeRegex(value); + prev.value += value; + append({ value }); + continue; + } + if (value === '"') { + state.quotes = state.quotes === 1 ? 0 : 1; + if (opts.keepQuotes === true) { + push({ type: "text", value }); + } + continue; + } + if (value === "(") { + increment("parens"); + push({ type: "paren", value }); + continue; + } + if (value === ")") { + if (state.parens === 0 && opts.strictBrackets === true) { + throw new SyntaxError(syntaxError("opening", "(")); + } + const extglob = extglobs[extglobs.length - 1]; + if (extglob && state.parens === extglob.parens + 1) { + extglobClose(extglobs.pop()); + continue; + } + push({ type: "paren", value, output: state.parens ? ")" : "\\)" }); + decrement("parens"); + continue; + } + if (value === "[") { + if (opts.nobracket === true || !remaining().includes("]")) { + if (opts.nobracket !== true && opts.strictBrackets === true) { + throw new SyntaxError(syntaxError("closing", "]")); + } + value = `\\${value}`; + } else { + increment("brackets"); + } + push({ type: "bracket", value }); + continue; + } + if (value === "]") { + if (opts.nobracket === true || prev && prev.type === "bracket" && prev.value.length === 1) { + push({ type: "text", value, output: `\\${value}` }); + continue; + } + if (state.brackets === 0) { + if (opts.strictBrackets === true) { + throw new SyntaxError(syntaxError("opening", "[")); + } + push({ type: "text", value, output: `\\${value}` }); + continue; + } + decrement("brackets"); + const prevValue = prev.value.slice(1); + if (prev.posix !== true && prevValue[0] === "^" && !prevValue.includes("/")) { + value = `/${value}`; + } + prev.value += value; + append({ value }); + if (opts.literalBrackets === false || utils.hasRegexChars(prevValue)) { + continue; + } + const escaped = utils.escapeRegex(prev.value); + state.output = state.output.slice(0, -prev.value.length); + if (opts.literalBrackets === true) { + state.output += escaped; + prev.value = escaped; + continue; + } + prev.value = `(${capture}${escaped}|${prev.value})`; + state.output += prev.value; + continue; + } + if (value === "{" && opts.nobrace !== true) { + increment("braces"); + const open = { + type: "brace", + value, + output: "(", + outputIndex: state.output.length, + tokensIndex: state.tokens.length + }; + braces.push(open); + push(open); + continue; + } + if (value === "}") { + const brace = braces[braces.length - 1]; + if (opts.nobrace === true || !brace) { + push({ type: "text", value, output: value }); + continue; + } + let output = ")"; + if (brace.dots === true) { + const arr = tokens.slice(); + const range = []; + for (let i = arr.length - 1; i >= 0; i--) { + tokens.pop(); + if (arr[i].type === "brace") { + break; + } + if (arr[i].type !== "dots") { + range.unshift(arr[i].value); + } + } + output = expandRange(range, opts); + state.backtrack = true; + } + if (brace.comma !== true && brace.dots !== true) { + const out = state.output.slice(0, brace.outputIndex); + const toks = state.tokens.slice(brace.tokensIndex); + brace.value = brace.output = "\\{"; + value = output = "\\}"; + state.output = out; + for (const t of toks) { + state.output += t.output || t.value; + } + } + push({ type: "brace", value, output }); + decrement("braces"); + braces.pop(); + continue; + } + if (value === "|") { + if (extglobs.length > 0) { + extglobs[extglobs.length - 1].conditions++; + } + push({ type: "text", value }); + continue; + } + if (value === ",") { + let output = value; + const brace = braces[braces.length - 1]; + if (brace && stack[stack.length - 1] === "braces") { + brace.comma = true; + output = "|"; + } + push({ type: "comma", value, output }); + continue; + } + if (value === "/") { + if (prev.type === "dot" && state.index === state.start + 1) { + state.start = state.index + 1; + state.consumed = ""; + state.output = ""; + tokens.pop(); + prev = bos; + continue; + } + push({ type: "slash", value, output: SLASH_LITERAL }); + continue; + } + if (value === ".") { + if (state.braces > 0 && prev.type === "dot") { + if (prev.value === ".") prev.output = DOT_LITERAL; + const brace = braces[braces.length - 1]; + prev.type = "dots"; + prev.output += value; + prev.value += value; + brace.dots = true; + continue; + } + if (state.braces + state.parens === 0 && prev.type !== "bos" && prev.type !== "slash") { + push({ type: "text", value, output: DOT_LITERAL }); + continue; + } + push({ type: "dot", value, output: DOT_LITERAL }); + continue; + } + if (value === "?") { + const isGroup = prev && prev.value === "("; + if (!isGroup && opts.noextglob !== true && peek() === "(" && peek(2) !== "?") { + extglobOpen("qmark", value); + continue; + } + if (prev && prev.type === "paren") { + const next = peek(); + let output = value; + if (prev.value === "(" && !/[!=<:]/.test(next) || next === "<" && !/<([!=]|\w+>)/.test(remaining())) { + output = `\\${value}`; + } + push({ type: "text", value, output }); + continue; + } + if (opts.dot !== true && (prev.type === "slash" || prev.type === "bos")) { + push({ type: "qmark", value, output: QMARK_NO_DOT }); + continue; + } + push({ type: "qmark", value, output: QMARK }); + continue; + } + if (value === "!") { + if (opts.noextglob !== true && peek() === "(") { + if (peek(2) !== "?" || !/[!=<:]/.test(peek(3))) { + extglobOpen("negate", value); + continue; + } + } + if (opts.nonegate !== true && state.index === 0) { + negate(); + continue; + } + } + if (value === "+") { + if (opts.noextglob !== true && peek() === "(" && peek(2) !== "?") { + extglobOpen("plus", value); + continue; + } + if (prev && prev.value === "(" || opts.regex === false) { + push({ type: "plus", value, output: PLUS_LITERAL }); + continue; + } + if (prev && (prev.type === "bracket" || prev.type === "paren" || prev.type === "brace") || state.parens > 0) { + push({ type: "plus", value }); + continue; + } + push({ type: "plus", value: PLUS_LITERAL }); + continue; + } + if (value === "@") { + if (opts.noextglob !== true && peek() === "(" && peek(2) !== "?") { + push({ type: "at", extglob: true, value, output: "" }); + continue; + } + push({ type: "text", value }); + continue; + } + if (value !== "*") { + if (value === "$" || value === "^") { + value = `\\${value}`; + } + const match = REGEX_NON_SPECIAL_CHARS.exec(remaining()); + if (match) { + value += match[0]; + state.index += match[0].length; + } + push({ type: "text", value }); + continue; + } + if (prev && (prev.type === "globstar" || prev.star === true)) { + prev.type = "star"; + prev.star = true; + prev.value += value; + prev.output = star; + state.backtrack = true; + state.globstar = true; + consume(value); + continue; + } + let rest = remaining(); + if (opts.noextglob !== true && /^\([^?]/.test(rest)) { + extglobOpen("star", value); + continue; + } + if (prev.type === "star") { + if (opts.noglobstar === true) { + consume(value); + continue; + } + const prior = prev.prev; + const before = prior.prev; + const isStart = prior.type === "slash" || prior.type === "bos"; + const afterStar = before && (before.type === "star" || before.type === "globstar"); + if (opts.bash === true && (!isStart || rest[0] && rest[0] !== "/")) { + push({ type: "star", value, output: "" }); + continue; + } + const isBrace = state.braces > 0 && (prior.type === "comma" || prior.type === "brace"); + const isExtglob = extglobs.length && (prior.type === "pipe" || prior.type === "paren"); + if (!isStart && prior.type !== "paren" && !isBrace && !isExtglob) { + push({ type: "star", value, output: "" }); + continue; + } + while (rest.slice(0, 3) === "/**") { + const after = input[state.index + 4]; + if (after && after !== "/") { + break; + } + rest = rest.slice(3); + consume("/**", 3); + } + if (prior.type === "bos" && eos()) { + prev.type = "globstar"; + prev.value += value; + prev.output = globstar(opts); + state.output = prev.output; + state.globstar = true; + consume(value); + continue; + } + if (prior.type === "slash" && prior.prev.type !== "bos" && !afterStar && eos()) { + state.output = state.output.slice(0, -(prior.output + prev.output).length); + prior.output = `(?:${prior.output}`; + prev.type = "globstar"; + prev.output = globstar(opts) + (opts.strictSlashes ? ")" : "|$)"); + prev.value += value; + state.globstar = true; + state.output += prior.output + prev.output; + consume(value); + continue; + } + if (prior.type === "slash" && prior.prev.type !== "bos" && rest[0] === "/") { + const end = rest[1] !== void 0 ? "|$" : ""; + state.output = state.output.slice(0, -(prior.output + prev.output).length); + prior.output = `(?:${prior.output}`; + prev.type = "globstar"; + prev.output = `${globstar(opts)}${SLASH_LITERAL}|${SLASH_LITERAL}${end})`; + prev.value += value; + state.output += prior.output + prev.output; + state.globstar = true; + consume(value + advance()); + push({ type: "slash", value: "/", output: "" }); + continue; + } + if (prior.type === "bos" && rest[0] === "/") { + prev.type = "globstar"; + prev.value += value; + prev.output = `(?:^|${SLASH_LITERAL}|${globstar(opts)}${SLASH_LITERAL})`; + state.output = prev.output; + state.globstar = true; + consume(value + advance()); + push({ type: "slash", value: "/", output: "" }); + continue; + } + state.output = state.output.slice(0, -prev.output.length); + prev.type = "globstar"; + prev.output = globstar(opts); + prev.value += value; + state.output += prev.output; + state.globstar = true; + consume(value); + continue; + } + const token = { type: "star", value, output: star }; + if (opts.bash === true) { + token.output = ".*?"; + if (prev.type === "bos" || prev.type === "slash") { + token.output = nodot + token.output; + } + push(token); + continue; + } + if (prev && (prev.type === "bracket" || prev.type === "paren") && opts.regex === true) { + token.output = value; + push(token); + continue; + } + if (state.index === state.start || prev.type === "slash" || prev.type === "dot") { + if (prev.type === "dot") { + state.output += NO_DOT_SLASH; + prev.output += NO_DOT_SLASH; + } else if (opts.dot === true) { + state.output += NO_DOTS_SLASH; + prev.output += NO_DOTS_SLASH; + } else { + state.output += nodot; + prev.output += nodot; + } + if (peek() !== "*") { + state.output += ONE_CHAR; + prev.output += ONE_CHAR; + } + } + push(token); + } + while (state.brackets > 0) { + if (opts.strictBrackets === true) throw new SyntaxError(syntaxError("closing", "]")); + state.output = utils.escapeLast(state.output, "["); + decrement("brackets"); + } + while (state.parens > 0) { + if (opts.strictBrackets === true) throw new SyntaxError(syntaxError("closing", ")")); + state.output = utils.escapeLast(state.output, "("); + decrement("parens"); + } + while (state.braces > 0) { + if (opts.strictBrackets === true) throw new SyntaxError(syntaxError("closing", "}")); + state.output = utils.escapeLast(state.output, "{"); + decrement("braces"); + } + if (opts.strictSlashes !== true && (prev.type === "star" || prev.type === "bracket")) { + push({ type: "maybe_slash", value: "", output: `${SLASH_LITERAL}?` }); + } + if (state.backtrack === true) { + state.output = ""; + for (const token of state.tokens) { + state.output += token.output != null ? token.output : token.value; + if (token.suffix) { + state.output += token.suffix; + } + } + } + return state; + }; + parse4.fastpaths = (input, options) => { + const opts = { ...options }; + const max = typeof opts.maxLength === "number" ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; + const len = input.length; + if (len > max) { + throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`); + } + input = REPLACEMENTS[input] || input; + const { + DOT_LITERAL, + SLASH_LITERAL, + ONE_CHAR, + DOTS_SLASH, + NO_DOT, + NO_DOTS, + NO_DOTS_SLASH, + STAR, + START_ANCHOR + } = constants.globChars(opts.windows); + const nodot = opts.dot ? NO_DOTS : NO_DOT; + const slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT; + const capture = opts.capture ? "" : "?:"; + const state = { negated: false, prefix: "" }; + let star = opts.bash === true ? ".*?" : STAR; + if (opts.capture) { + star = `(${star})`; + } + const globstar = (opts2) => { + if (opts2.noglobstar === true) return star; + return `(${capture}(?:(?!${START_ANCHOR}${opts2.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`; + }; + const create = (str) => { + switch (str) { + case "*": + return `${nodot}${ONE_CHAR}${star}`; + case ".*": + return `${DOT_LITERAL}${ONE_CHAR}${star}`; + case "*.*": + return `${nodot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`; + case "*/*": + return `${nodot}${star}${SLASH_LITERAL}${ONE_CHAR}${slashDot}${star}`; + case "**": + return nodot + globstar(opts); + case "**/*": + return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${ONE_CHAR}${star}`; + case "**/*.*": + return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`; + case "**/.*": + return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${DOT_LITERAL}${ONE_CHAR}${star}`; + default: { + const match = /^(.*?)\.(\w+)$/.exec(str); + if (!match) return; + const source2 = create(match[1]); + if (!source2) return; + return source2 + DOT_LITERAL + match[2]; + } + } + }; + const output = utils.removePrefix(input, state); + let source = create(output); + if (source && opts.strictSlashes !== true) { + source += `${SLASH_LITERAL}?`; + } + return source; + }; + module2.exports = parse4; + } +}); + +// node_modules/picomatch/lib/picomatch.js +var require_picomatch = __commonJS({ + "node_modules/picomatch/lib/picomatch.js"(exports2, module2) { + "use strict"; + var scan = require_scan(); + var parse4 = require_parse(); + var utils = require_utils(); + var constants = require_constants(); + var isObject = (val) => val && typeof val === "object" && !Array.isArray(val); + var picomatch3 = (glob, options, returnState = false) => { + if (Array.isArray(glob)) { + const fns = glob.map((input) => picomatch3(input, options, returnState)); + const arrayMatcher = (str) => { + for (const isMatch of fns) { + const state2 = isMatch(str); + if (state2) return state2; + } + return false; + }; + return arrayMatcher; + } + const isState = isObject(glob) && glob.tokens && glob.input; + if (glob === "" || typeof glob !== "string" && !isState) { + throw new TypeError("Expected pattern to be a non-empty string"); + } + const opts = options || {}; + const posix = opts.windows; + const regex = isState ? picomatch3.compileRe(glob, options) : picomatch3.makeRe(glob, options, false, true); + const state = regex.state; + delete regex.state; + let isIgnored = () => false; + if (opts.ignore) { + const ignoreOpts = { ...options, ignore: null, onMatch: null, onResult: null }; + isIgnored = picomatch3(opts.ignore, ignoreOpts, returnState); + } + const matcher = (input, returnObject = false) => { + const { isMatch, match, output } = picomatch3.test(input, regex, options, { glob, posix }); + const result = { glob, state, regex, posix, input, output, match, isMatch }; + if (typeof opts.onResult === "function") { + opts.onResult(result); + } + if (isMatch === false) { + result.isMatch = false; + return returnObject ? result : false; + } + if (isIgnored(input)) { + if (typeof opts.onIgnore === "function") { + opts.onIgnore(result); + } + result.isMatch = false; + return returnObject ? result : false; + } + if (typeof opts.onMatch === "function") { + opts.onMatch(result); + } + return returnObject ? result : true; + }; + if (returnState) { + matcher.state = state; + } + return matcher; + }; + picomatch3.test = (input, regex, options, { glob, posix } = {}) => { + if (typeof input !== "string") { + throw new TypeError("Expected input to be a string"); + } + if (input === "") { + return { isMatch: false, output: "" }; + } + const opts = options || {}; + const format = opts.format || (posix ? utils.toPosixSlashes : null); + let match = input === glob; + let output = match && format ? format(input) : input; + if (match === false) { + output = format ? format(input) : input; + match = output === glob; + } + if (match === false || opts.capture === true) { + if (opts.matchBase === true || opts.basename === true) { + match = picomatch3.matchBase(input, regex, options, posix); + } else { + match = regex.exec(output); + } + } + return { isMatch: Boolean(match), match, output }; + }; + picomatch3.matchBase = (input, glob, options) => { + const regex = glob instanceof RegExp ? glob : picomatch3.makeRe(glob, options); + return regex.test(utils.basename(input)); + }; + picomatch3.isMatch = (str, patterns, options) => picomatch3(patterns, options)(str); + picomatch3.parse = (pattern, options) => { + if (Array.isArray(pattern)) return pattern.map((p) => picomatch3.parse(p, options)); + return parse4(pattern, { ...options, fastpaths: false }); + }; + picomatch3.scan = (input, options) => scan(input, options); + picomatch3.compileRe = (state, options, returnOutput = false, returnState = false) => { + if (returnOutput === true) { + return state.output; + } + const opts = options || {}; + const prepend = opts.contains ? "" : "^"; + const append = opts.contains ? "" : "$"; + let source = `${prepend}(?:${state.output})${append}`; + if (state && state.negated === true) { + source = `^(?!${source}).*$`; + } + const regex = picomatch3.toRegex(source, options); + if (returnState === true) { + regex.state = state; + } + return regex; + }; + picomatch3.makeRe = (input, options = {}, returnOutput = false, returnState = false) => { + if (!input || typeof input !== "string") { + throw new TypeError("Expected a non-empty string"); + } + let parsed = { negated: false, fastpaths: true }; + if (options.fastpaths !== false && (input[0] === "." || input[0] === "*")) { + parsed.output = parse4.fastpaths(input, options); + } + if (!parsed.output) { + parsed = parse4(input, options); + } + return picomatch3.compileRe(parsed, options, returnOutput, returnState); + }; + picomatch3.toRegex = (source, options) => { + try { + const opts = options || {}; + return new RegExp(source, opts.flags || (opts.nocase ? "i" : "")); + } catch (err) { + if (options && options.debug === true) throw err; + return /$^/; + } + }; + picomatch3.constants = constants; + module2.exports = picomatch3; + } +}); + +// node_modules/picomatch/index.js +var require_picomatch2 = __commonJS({ + "node_modules/picomatch/index.js"(exports2, module2) { + "use strict"; + var pico = require_picomatch(); + var utils = require_utils(); + function picomatch3(glob, options, returnState = false) { + if (options && (options.windows === null || options.windows === void 0)) { + options = { ...options, windows: utils.isWindows() }; + } + return pico(glob, options, returnState); + } + Object.assign(picomatch3, pico); + module2.exports = picomatch3; + } +}); + +// src/config.ts +function getConfigPath(projectRoot) { + return path7.join(projectRoot, ".codegraph", CONFIG_FILENAME); +} +function isSafeRegex(pattern) { + if (pattern.length > 500) return false; + if (/([+*}])\s*[+*{]/.test(pattern)) return false; + if (/\([^)]*[+*][^)]*\)[+*{]/.test(pattern)) return false; + try { + new RegExp(pattern); + return true; + } catch { + return false; + } +} +function validateConfig(config) { + if (typeof config !== "object" || config === null) { + return false; + } + const c = config; + if (typeof c.version !== "number") return false; + if (typeof c.rootDir !== "string") return false; + if (!Array.isArray(c.include)) return false; + if (!Array.isArray(c.exclude)) return false; + if (!Array.isArray(c.languages)) return false; + if (!Array.isArray(c.frameworks)) return false; + if (typeof c.maxFileSize !== "number") return false; + if (typeof c.extractDocstrings !== "boolean") return false; + if (typeof c.trackCallSites !== "boolean") return false; + if (!c.include.every((p) => typeof p === "string")) return false; + if (!c.exclude.every((p) => typeof p === "string")) return false; + const validLanguages = [ + "typescript", + "javascript", + "python", + "go", + "rust", + "java", + "svelte", + "unknown" + ]; + if (!c.languages.every((l) => validLanguages.includes(l))) return false; + for (const fw of c.frameworks) { + if (typeof fw !== "object" || fw === null) return false; + const framework = fw; + if (typeof framework.name !== "string") return false; + } + if (c.customPatterns !== void 0) { + if (!Array.isArray(c.customPatterns)) return false; + for (const pattern of c.customPatterns) { + if (typeof pattern !== "object" || pattern === null) return false; + const p = pattern; + if (typeof p.name !== "string") return false; + if (typeof p.pattern !== "string") return false; + if (typeof p.kind !== "string") return false; + if (!isSafeRegex(p.pattern)) return false; + } + } + return true; +} +function mergeConfig(defaults, overrides) { + return { + version: overrides.version ?? defaults.version, + rootDir: overrides.rootDir ?? defaults.rootDir, + include: overrides.include ?? defaults.include, + exclude: overrides.exclude ?? defaults.exclude, + languages: overrides.languages ?? defaults.languages, + frameworks: overrides.frameworks ?? defaults.frameworks, + maxFileSize: overrides.maxFileSize ?? defaults.maxFileSize, + extractDocstrings: overrides.extractDocstrings ?? defaults.extractDocstrings, + trackCallSites: overrides.trackCallSites ?? defaults.trackCallSites, + customPatterns: overrides.customPatterns ?? defaults.customPatterns + }; +} +function loadConfig(projectRoot) { + const configPath = getConfigPath(projectRoot); + if (!fs5.existsSync(configPath)) { + return { + ...DEFAULT_CONFIG, + rootDir: projectRoot + }; + } + try { + const content = fs5.readFileSync(configPath, "utf-8"); + const parsed = JSON.parse(content); + const merged = mergeConfig(DEFAULT_CONFIG, parsed); + merged.rootDir = projectRoot; + if (!validateConfig(merged)) { + throw new Error("Invalid configuration format"); + } + return merged; + } catch (error) { + if (error instanceof SyntaxError) { + throw new Error(`Invalid JSON in config file: ${configPath}`); + } + throw error; + } +} +function saveConfig(projectRoot, config) { + const configPath = getConfigPath(projectRoot); + const dir = path7.dirname(configPath); + if (!fs5.existsSync(dir)) { + fs5.mkdirSync(dir, { recursive: true }); + } + const toSave = { ...config }; + delete toSave.rootDir; + const content = JSON.stringify(toSave, null, 2); + const tmpPath = configPath + ".tmp"; + fs5.writeFileSync(tmpPath, content, "utf-8"); + fs5.renameSync(tmpPath, configPath); +} +function createDefaultConfig(projectRoot) { + return { + ...DEFAULT_CONFIG, + rootDir: projectRoot + }; +} +var fs5, path7, CONFIG_FILENAME; +var init_config = __esm({ + "src/config.ts"() { + "use strict"; + fs5 = __toESM(require("fs")); + path7 = __toESM(require("path")); + init_types(); + init_utils(); + CONFIG_FILENAME = "config.json"; + } +}); + +// src/extraction/grammars.ts +async function initGrammars() { + if (parserInitialized) return; + if (!initPromise) { + initPromise = import_web_tree_sitter.Parser.init().then(() => { + parserInitialized = true; + }); + } + await initPromise; +} +async function loadGrammarsForLanguages(languages) { + if (!parserInitialized) { + await initGrammars(); + } + const toLoad = [...new Set(languages)].filter( + (lang) => lang in WASM_GRAMMAR_FILES && !languageCache.has(lang) && !unavailableGrammarErrors.has(lang) + ); + if (toLoad.length === 0) return; + const promises = toLoad.map((lang) => { + if (loadingPromises.has(lang)) { + return loadingPromises.get(lang); + } + const p = loadQueue.then(async () => { + if (languageCache.has(lang) || unavailableGrammarErrors.has(lang)) return; + const wasmFile = WASM_GRAMMAR_FILES[lang]; + try { + const wasmPath = lang === "pascal" || lang === "scala" ? path8.join(__dirname, "wasm", wasmFile) : require.resolve(`tree-sitter-wasms/out/${wasmFile}`); + const language = await import_web_tree_sitter.Language.load(wasmPath); + languageCache.set(lang, language); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + console.warn( + `[CodeGraph] Failed to load ${lang} grammar \u2014 parsing will be unavailable: ${message}` + ); + unavailableGrammarErrors.set(lang, message); + } + }).finally(() => { + loadingPromises.delete(lang); + }); + loadQueue = p.catch(() => { + }); + loadingPromises.set(lang, p); + return p; + }); + await Promise.all(promises); +} +async function loadAllGrammars() { + const allLanguages = Object.keys(WASM_GRAMMAR_FILES); + await loadGrammarsForLanguages(allLanguages); +} +function getParser(language) { + if (parserCache.has(language)) { + return parserCache.get(language); + } + const lang = languageCache.get(language); + if (!lang) { + return null; + } + const parser = new import_web_tree_sitter.Parser(); + parser.setLanguage(lang); + parserCache.set(language, parser); + return parser; +} +function detectLanguage(filePath, source) { + const ext = filePath.substring(filePath.lastIndexOf(".")).toLowerCase(); + const lang = EXTENSION_MAP[ext] || "unknown"; + if (lang === "c" && ext === ".h" && source) { + if (looksLikeCpp(source)) return "cpp"; + } + return lang; +} +function looksLikeCpp(source) { + const sample = source.substring(0, 8192); + return /\bnamespace\b|\bclass\s+\w+\s*[:{]|\btemplate\s*<|\b(?:public|private|protected)\s*:|\bvirtual\b|\busing\s+(?:namespace\b|\w+\s*=)/.test(sample); +} +function isLanguageSupported(language) { + if (language === "svelte") return true; + if (language === "vue") return true; + if (language === "liquid") return true; + if (language === "unknown") return false; + return language in WASM_GRAMMAR_FILES; +} +function isGrammarLoaded(language) { + if (language === "svelte" || language === "vue" || language === "liquid") return true; + return languageCache.has(language); +} +function getSupportedLanguages() { + return [...Object.keys(WASM_GRAMMAR_FILES), "svelte", "vue", "liquid"]; +} +var path8, import_web_tree_sitter, WASM_GRAMMAR_FILES, EXTENSION_MAP, parserCache, languageCache, unavailableGrammarErrors, parserInitialized, initPromise, loadQueue, loadingPromises; +var init_grammars = __esm({ + "src/extraction/grammars.ts"() { + "use strict"; + path8 = __toESM(require("path")); + import_web_tree_sitter = require("web-tree-sitter"); + WASM_GRAMMAR_FILES = { + typescript: "tree-sitter-typescript.wasm", + tsx: "tree-sitter-tsx.wasm", + javascript: "tree-sitter-javascript.wasm", + jsx: "tree-sitter-javascript.wasm", + python: "tree-sitter-python.wasm", + go: "tree-sitter-go.wasm", + rust: "tree-sitter-rust.wasm", + java: "tree-sitter-java.wasm", + c: "tree-sitter-c.wasm", + cpp: "tree-sitter-cpp.wasm", + csharp: "tree-sitter-c_sharp.wasm", + php: "tree-sitter-php.wasm", + ruby: "tree-sitter-ruby.wasm", + swift: "tree-sitter-swift.wasm", + kotlin: "tree-sitter-kotlin.wasm", + dart: "tree-sitter-dart.wasm", + pascal: "tree-sitter-pascal.wasm", + scala: "tree-sitter-scala.wasm" + }; + EXTENSION_MAP = { + ".ts": "typescript", + ".tsx": "tsx", + ".js": "javascript", + ".mjs": "javascript", + ".cjs": "javascript", + ".jsx": "jsx", + ".py": "python", + ".pyw": "python", + ".go": "go", + ".rs": "rust", + ".java": "java", + ".c": "c", + ".h": "c", + // Could also be C++, defaulting to C + ".cpp": "cpp", + ".cc": "cpp", + ".cxx": "cpp", + ".hpp": "cpp", + ".hxx": "cpp", + ".cs": "csharp", + ".php": "php", + ".rb": "ruby", + ".rake": "ruby", + ".swift": "swift", + ".kt": "kotlin", + ".kts": "kotlin", + ".dart": "dart", + ".liquid": "liquid", + ".svelte": "svelte", + ".vue": "vue", + ".pas": "pascal", + ".dpr": "pascal", + ".dpk": "pascal", + ".lpr": "pascal", + ".dfm": "pascal", + ".fmx": "pascal", + ".scala": "scala", + ".sc": "scala" + }; + parserCache = /* @__PURE__ */ new Map(); + languageCache = /* @__PURE__ */ new Map(); + unavailableGrammarErrors = /* @__PURE__ */ new Map(); + parserInitialized = false; + initPromise = null; + loadQueue = Promise.resolve(); + loadingPromises = /* @__PURE__ */ new Map(); + } +}); + +// src/extraction/tree-sitter-helpers.ts +function generateNodeId(filePath, kind, name, line) { + const hash = crypto.createHash("sha256").update(`${filePath}:${kind}:${name}:${line}`).digest("hex").substring(0, 32); + return `${kind}:${hash}`; +} +function getNodeText(node, source) { + return source.substring(node.startIndex, node.endIndex); +} +function getChildByField(node, fieldName) { + return node.childForFieldName(fieldName); +} +function getPrecedingDocstring(node, source) { + let sibling = node.previousNamedSibling; + const comments = []; + while (sibling) { + if (sibling.type === "comment" || sibling.type === "line_comment" || sibling.type === "block_comment" || sibling.type === "documentation_comment") { + comments.unshift(getNodeText(sibling, source)); + sibling = sibling.previousNamedSibling; + } else { + break; + } + } + if (comments.length === 0) return void 0; + return comments.map( + (c) => c.replace(/^\/\*\*?|\*\/$/g, "").replace(/^\/\/\s?/gm, "").replace(/^\s*\*\s?/gm, "").trim() + ).join("\n").trim(); +} +var crypto; +var init_tree_sitter_helpers = __esm({ + "src/extraction/tree-sitter-helpers.ts"() { + "use strict"; + crypto = __toESM(require("crypto")); + } +}); + +// src/extraction/languages/typescript.ts +var typescriptExtractor; +var init_typescript = __esm({ + "src/extraction/languages/typescript.ts"() { + "use strict"; + init_tree_sitter_helpers(); + typescriptExtractor = { + functionTypes: ["function_declaration", "arrow_function", "function_expression"], + classTypes: ["class_declaration", "abstract_class_declaration"], + methodTypes: ["method_definition", "public_field_definition"], + interfaceTypes: ["interface_declaration"], + structTypes: [], + enumTypes: ["enum_declaration"], + enumMemberTypes: ["property_identifier", "enum_assignment"], + typeAliasTypes: ["type_alias_declaration"], + importTypes: ["import_statement"], + callTypes: ["call_expression"], + variableTypes: ["lexical_declaration", "variable_declaration"], + nameField: "name", + bodyField: "body", + resolveBody: (node, bodyField) => { + if (node.type === "public_field_definition") { + for (let i = 0; i < node.namedChildCount; i++) { + const child = node.namedChild(i); + if (!child) continue; + if (child.type === "arrow_function" || child.type === "function_expression") { + return getChildByField(child, bodyField); + } + if (child.type === "call_expression") { + const args = getChildByField(child, "arguments"); + if (args) { + for (let j = 0; j < args.namedChildCount; j++) { + const arg = args.namedChild(j); + if (arg && (arg.type === "arrow_function" || arg.type === "function_expression")) { + return getChildByField(arg, bodyField); + } + } + } + } + } + } + return null; + }, + paramsField: "parameters", + returnField: "return_type", + getSignature: (node, source) => { + const params = getChildByField(node, "parameters"); + const returnType = getChildByField(node, "return_type"); + if (!params) return void 0; + let sig = getNodeText(params, source); + if (returnType) { + sig += ": " + getNodeText(returnType, source).replace(/^:\s*/, ""); + } + return sig; + }, + getVisibility: (node) => { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child?.type === "accessibility_modifier") { + const text = child.text; + if (text === "public") return "public"; + if (text === "private") return "private"; + if (text === "protected") return "protected"; + } + } + return void 0; + }, + isExported: (node, _source) => { + let current = node.parent; + while (current) { + if (current.type === "export_statement") return true; + current = current.parent; + } + return false; + }, + isAsync: (node) => { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child?.type === "async") return true; + } + return false; + }, + isStatic: (node) => { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child?.type === "static") return true; + } + return false; + }, + isConst: (node) => { + if (node.type === "lexical_declaration") { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child?.type === "const") return true; + } + } + return false; + }, + extractImport: (node, source) => { + const sourceField = node.childForFieldName("source"); + if (sourceField) { + const moduleName = source.substring(sourceField.startIndex, sourceField.endIndex).replace(/['"]/g, ""); + if (moduleName) { + return { moduleName, signature: source.substring(node.startIndex, node.endIndex).trim() }; + } + } + return null; + } + }; + } +}); + +// src/extraction/languages/javascript.ts +var javascriptExtractor; +var init_javascript = __esm({ + "src/extraction/languages/javascript.ts"() { + "use strict"; + init_tree_sitter_helpers(); + javascriptExtractor = { + functionTypes: ["function_declaration", "arrow_function", "function_expression"], + classTypes: ["class_declaration"], + methodTypes: ["method_definition", "field_definition"], + interfaceTypes: [], + structTypes: [], + enumTypes: [], + typeAliasTypes: [], + importTypes: ["import_statement"], + callTypes: ["call_expression"], + variableTypes: ["lexical_declaration", "variable_declaration"], + nameField: "name", + bodyField: "body", + resolveBody: (node, bodyField) => { + if (node.type === "field_definition") { + for (let i = 0; i < node.namedChildCount; i++) { + const child = node.namedChild(i); + if (!child) continue; + if (child.type === "arrow_function" || child.type === "function_expression") { + return getChildByField(child, bodyField); + } + if (child.type === "call_expression") { + const args = getChildByField(child, "arguments"); + if (args) { + for (let j = 0; j < args.namedChildCount; j++) { + const arg = args.namedChild(j); + if (arg && (arg.type === "arrow_function" || arg.type === "function_expression")) { + return getChildByField(arg, bodyField); + } + } + } + } + } + } + return null; + }, + paramsField: "parameters", + getSignature: (node, source) => { + const params = getChildByField(node, "parameters"); + return params ? getNodeText(params, source) : void 0; + }, + isExported: (node, _source) => { + let current = node.parent; + while (current) { + if (current.type === "export_statement") return true; + current = current.parent; + } + return false; + }, + isAsync: (node) => { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child?.type === "async") return true; + } + return false; + }, + isConst: (node) => { + if (node.type === "lexical_declaration") { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child?.type === "const") return true; + } + } + return false; + }, + extractImport: (node, source) => { + const sourceField = node.childForFieldName("source"); + if (sourceField) { + const moduleName = source.substring(sourceField.startIndex, sourceField.endIndex).replace(/['"]/g, ""); + if (moduleName) { + return { moduleName, signature: source.substring(node.startIndex, node.endIndex).trim() }; + } + } + return null; + } + }; + } +}); + +// src/extraction/languages/python.ts +var pythonExtractor; +var init_python = __esm({ + "src/extraction/languages/python.ts"() { + "use strict"; + init_tree_sitter_helpers(); + pythonExtractor = { + functionTypes: ["function_definition"], + classTypes: ["class_definition"], + methodTypes: ["function_definition"], + // Methods are functions inside classes + interfaceTypes: [], + structTypes: [], + enumTypes: [], + typeAliasTypes: [], + importTypes: ["import_statement", "import_from_statement"], + callTypes: ["call"], + variableTypes: ["assignment"], + // Python uses assignment for variable declarations + nameField: "name", + bodyField: "body", + paramsField: "parameters", + returnField: "return_type", + getSignature: (node, source) => { + const params = getChildByField(node, "parameters"); + const returnType = getChildByField(node, "return_type"); + if (!params) return void 0; + let sig = getNodeText(params, source); + if (returnType) { + sig += " -> " + getNodeText(returnType, source); + } + return sig; + }, + isAsync: (node) => { + const prev = node.previousSibling; + return prev?.type === "async"; + }, + isStatic: (node) => { + const prev = node.previousNamedSibling; + if (prev?.type === "decorator") { + const text = prev.text; + return text.includes("staticmethod"); + } + return false; + }, + extractImport: (node, source) => { + const importText = source.substring(node.startIndex, node.endIndex).trim(); + if (node.type === "import_from_statement") { + const moduleNode = node.childForFieldName("module_name"); + if (moduleNode) { + return { moduleName: source.substring(moduleNode.startIndex, moduleNode.endIndex), signature: importText }; + } + } + return null; + } + }; + } +}); + +// src/extraction/languages/go.ts +var goExtractor; +var init_go = __esm({ + "src/extraction/languages/go.ts"() { + "use strict"; + init_tree_sitter_helpers(); + goExtractor = { + functionTypes: ["function_declaration"], + classTypes: [], + // Go doesn't have classes + methodTypes: ["method_declaration"], + interfaceTypes: [], + // Handled via type_spec → resolveTypeAliasKind + structTypes: [], + // Handled via type_spec → resolveTypeAliasKind + enumTypes: [], + typeAliasTypes: ["type_spec"], + // Go type declarations + importTypes: ["import_declaration"], + callTypes: ["call_expression"], + variableTypes: ["var_declaration", "short_var_declaration", "const_declaration"], + methodsAreTopLevel: true, + nameField: "name", + bodyField: "body", + paramsField: "parameters", + returnField: "result", + getSignature: (node, source) => { + const params = getChildByField(node, "parameters"); + const result = getChildByField(node, "result"); + if (!params) return void 0; + let sig = getNodeText(params, source); + if (result) { + sig += " " + getNodeText(result, source); + } + return sig; + }, + resolveTypeAliasKind: (node, _source) => { + const typeChild = getChildByField(node, "type"); + if (!typeChild) return void 0; + if (typeChild.type === "struct_type") return "struct"; + if (typeChild.type === "interface_type") return "interface"; + return void 0; + }, + getReceiverType: (node, source) => { + const receiver = getChildByField(node, "receiver"); + if (!receiver) return void 0; + const text = getNodeText(receiver, source); + const match = text.match(/\*?\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)/); + return match?.[1]; + } + }; + } +}); + +// src/extraction/languages/rust.ts +var rustExtractor; +var init_rust = __esm({ + "src/extraction/languages/rust.ts"() { + "use strict"; + init_tree_sitter_helpers(); + rustExtractor = { + functionTypes: ["function_item"], + classTypes: [], + // Rust has impl blocks + methodTypes: ["function_item"], + // Methods are functions in impl blocks + interfaceTypes: ["trait_item"], + structTypes: ["struct_item"], + enumTypes: ["enum_item"], + enumMemberTypes: ["enum_variant"], + typeAliasTypes: ["type_item"], + // Rust type aliases + importTypes: ["use_declaration"], + callTypes: ["call_expression"], + variableTypes: ["let_declaration", "const_item", "static_item"], + interfaceKind: "trait", + nameField: "name", + bodyField: "body", + paramsField: "parameters", + returnField: "return_type", + getSignature: (node, source) => { + const params = getChildByField(node, "parameters"); + const returnType = getChildByField(node, "return_type"); + if (!params) return void 0; + let sig = getNodeText(params, source); + if (returnType) { + sig += " -> " + getNodeText(returnType, source); + } + return sig; + }, + isAsync: (node) => { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child?.type === "async") return true; + } + return false; + }, + getVisibility: (node) => { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child?.type === "visibility_modifier") { + return child.text.includes("pub") ? "public" : "private"; + } + } + return "private"; + }, + getReceiverType: (node, source) => { + let parent = node.parent; + while (parent) { + if (parent.type === "impl_item") { + const children = parent.namedChildren; + const typeIdents = children.filter( + (c) => c.type === "type_identifier" + ); + if (typeIdents.length > 0) { + const typeNode = typeIdents[typeIdents.length - 1]; + return source.substring(typeNode.startIndex, typeNode.endIndex); + } + const genericType = children.find( + (c) => c.type === "generic_type" + ); + if (genericType) { + const innerType = genericType.namedChildren.find( + (c) => c.type === "type_identifier" + ); + if (innerType) { + return source.substring(innerType.startIndex, innerType.endIndex); + } + } + return void 0; + } + parent = parent.parent; + } + return void 0; + }, + extractImport: (node, source) => { + const importText = source.substring(node.startIndex, node.endIndex).trim(); + const getRootModule = (scopedNode) => { + const firstChild = scopedNode.namedChild(0); + if (!firstChild) return source.substring(scopedNode.startIndex, scopedNode.endIndex); + if (firstChild.type === "identifier" || firstChild.type === "crate" || firstChild.type === "super" || firstChild.type === "self") { + return source.substring(firstChild.startIndex, firstChild.endIndex); + } else if (firstChild.type === "scoped_identifier") { + return getRootModule(firstChild); + } + return source.substring(firstChild.startIndex, firstChild.endIndex); + }; + const useArg = node.namedChildren.find( + (c) => c.type === "scoped_use_list" || c.type === "scoped_identifier" || c.type === "use_list" || c.type === "identifier" + ); + if (useArg) { + return { moduleName: getRootModule(useArg), signature: importText }; + } + return null; + } + }; + } +}); + +// src/extraction/languages/java.ts +var javaExtractor; +var init_java = __esm({ + "src/extraction/languages/java.ts"() { + "use strict"; + init_tree_sitter_helpers(); + javaExtractor = { + functionTypes: [], + classTypes: ["class_declaration"], + methodTypes: ["method_declaration", "constructor_declaration"], + interfaceTypes: ["interface_declaration"], + structTypes: [], + enumTypes: ["enum_declaration"], + enumMemberTypes: ["enum_constant"], + typeAliasTypes: [], + importTypes: ["import_declaration"], + callTypes: ["method_invocation"], + variableTypes: ["local_variable_declaration"], + fieldTypes: ["field_declaration"], + nameField: "name", + bodyField: "body", + paramsField: "parameters", + returnField: "type", + getSignature: (node, source) => { + const params = getChildByField(node, "parameters"); + const returnType = getChildByField(node, "type"); + if (!params) return void 0; + const paramsText = getNodeText(params, source); + return returnType ? getNodeText(returnType, source) + " " + paramsText : paramsText; + }, + getVisibility: (node) => { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child?.type === "modifiers") { + const text = child.text; + if (text.includes("public")) return "public"; + if (text.includes("private")) return "private"; + if (text.includes("protected")) return "protected"; + } + } + return void 0; + }, + isStatic: (node) => { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child?.type === "modifiers" && child.text.includes("static")) { + return true; + } + } + return false; + }, + extractImport: (node, source) => { + const importText = source.substring(node.startIndex, node.endIndex).trim(); + const scopedId = node.namedChildren.find((c) => c.type === "scoped_identifier"); + if (scopedId) { + const moduleName = source.substring(scopedId.startIndex, scopedId.endIndex); + return { moduleName, signature: importText }; + } + return null; + } + }; + } +}); + +// src/extraction/languages/c-cpp.ts +var cExtractor, cppExtractor; +var init_c_cpp = __esm({ + "src/extraction/languages/c-cpp.ts"() { + "use strict"; + init_tree_sitter_helpers(); + cExtractor = { + functionTypes: ["function_definition"], + classTypes: [], + methodTypes: [], + interfaceTypes: [], + structTypes: ["struct_specifier"], + enumTypes: ["enum_specifier"], + enumMemberTypes: ["enumerator"], + typeAliasTypes: ["type_definition"], + // typedef + importTypes: ["preproc_include"], + callTypes: ["call_expression"], + variableTypes: ["declaration"], + nameField: "declarator", + bodyField: "body", + paramsField: "parameters", + resolveTypeAliasKind: (node, _source) => { + for (let i = 0; i < node.namedChildCount; i++) { + const child = node.namedChild(i); + if (!child) continue; + if (child.type === "enum_specifier" && getChildByField(child, "body")) return "enum"; + if (child.type === "struct_specifier" && getChildByField(child, "body")) return "struct"; + } + return void 0; + }, + extractImport: (node, source) => { + const importText = source.substring(node.startIndex, node.endIndex).trim(); + const systemLib = node.namedChildren.find((c) => c.type === "system_lib_string"); + if (systemLib) { + return { moduleName: getNodeText(systemLib, source).replace(/^<|>$/g, ""), signature: importText }; + } + const stringLiteral = node.namedChildren.find((c) => c.type === "string_literal"); + if (stringLiteral) { + const stringContent = stringLiteral.namedChildren.find((c) => c.type === "string_content"); + if (stringContent) { + return { moduleName: getNodeText(stringContent, source), signature: importText }; + } + } + return null; + } + }; + cppExtractor = { + functionTypes: ["function_definition"], + classTypes: ["class_specifier"], + methodTypes: ["function_definition"], + interfaceTypes: [], + structTypes: ["struct_specifier"], + enumTypes: ["enum_specifier"], + enumMemberTypes: ["enumerator"], + typeAliasTypes: ["type_definition", "alias_declaration"], + // typedef and using + importTypes: ["preproc_include"], + callTypes: ["call_expression"], + variableTypes: ["declaration"], + nameField: "declarator", + bodyField: "body", + paramsField: "parameters", + getVisibility: (node) => { + const parent = node.parent; + if (parent) { + for (let i = 0; i < parent.childCount; i++) { + const child = parent.child(i); + if (child?.type === "access_specifier") { + const text = child.text; + if (text.includes("public")) return "public"; + if (text.includes("private")) return "private"; + if (text.includes("protected")) return "protected"; + } + } + } + return void 0; + }, + resolveTypeAliasKind: (node, _source) => { + for (let i = 0; i < node.namedChildCount; i++) { + const child = node.namedChild(i); + if (!child) continue; + if (child.type === "enum_specifier" && getChildByField(child, "body")) return "enum"; + if (child.type === "struct_specifier" && getChildByField(child, "body")) return "struct"; + } + return void 0; + }, + isMisparsedFunction: (name) => { + if (name.startsWith("namespace")) return true; + const cppKeywords = ["switch", "if", "for", "while", "do", "case", "return"]; + return cppKeywords.includes(name); + }, + extractImport: (node, source) => { + const importText = source.substring(node.startIndex, node.endIndex).trim(); + const systemLib = node.namedChildren.find((c) => c.type === "system_lib_string"); + if (systemLib) { + return { moduleName: getNodeText(systemLib, source).replace(/^<|>$/g, ""), signature: importText }; + } + const stringLiteral = node.namedChildren.find((c) => c.type === "string_literal"); + if (stringLiteral) { + const stringContent = stringLiteral.namedChildren.find((c) => c.type === "string_content"); + if (stringContent) { + return { moduleName: getNodeText(stringContent, source), signature: importText }; + } + } + return null; + } + }; + } +}); + +// src/extraction/languages/csharp.ts +var csharpExtractor; +var init_csharp = __esm({ + "src/extraction/languages/csharp.ts"() { + "use strict"; + init_tree_sitter_helpers(); + csharpExtractor = { + functionTypes: [], + classTypes: ["class_declaration"], + methodTypes: ["method_declaration", "constructor_declaration"], + interfaceTypes: ["interface_declaration"], + structTypes: ["struct_declaration"], + enumTypes: ["enum_declaration"], + enumMemberTypes: ["enum_member_declaration"], + typeAliasTypes: [], + importTypes: ["using_directive"], + callTypes: ["invocation_expression"], + variableTypes: ["local_declaration_statement"], + fieldTypes: ["field_declaration"], + propertyTypes: ["property_declaration"], + nameField: "name", + bodyField: "body", + paramsField: "parameter_list", + getVisibility: (node) => { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child?.type === "modifier") { + const text = child.text; + if (text === "public") return "public"; + if (text === "private") return "private"; + if (text === "protected") return "protected"; + if (text === "internal") return "internal"; + } + } + return "private"; + }, + isStatic: (node) => { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child?.type === "modifier" && child.text === "static") { + return true; + } + } + return false; + }, + isAsync: (node) => { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child?.type === "modifier" && child.text === "async") { + return true; + } + } + return false; + }, + extractImport: (node, source) => { + const importText = source.substring(node.startIndex, node.endIndex).trim(); + const qualifiedName = node.namedChildren.find((c) => c.type === "qualified_name"); + if (qualifiedName) { + return { moduleName: getNodeText(qualifiedName, source), signature: importText }; + } + const identifier = node.namedChildren.find((c) => c.type === "identifier"); + if (identifier) { + return { moduleName: getNodeText(identifier, source), signature: importText }; + } + return null; + } + }; + } +}); + +// src/extraction/languages/php.ts +var phpExtractor; +var init_php = __esm({ + "src/extraction/languages/php.ts"() { + "use strict"; + init_tree_sitter_helpers(); + phpExtractor = { + functionTypes: ["function_definition"], + classTypes: ["class_declaration", "trait_declaration"], + methodTypes: ["method_declaration"], + interfaceTypes: ["interface_declaration"], + structTypes: [], + enumTypes: ["enum_declaration"], + enumMemberTypes: ["enum_case"], + typeAliasTypes: [], + importTypes: ["namespace_use_declaration"], + callTypes: ["function_call_expression", "member_call_expression", "scoped_call_expression"], + variableTypes: ["const_declaration"], + fieldTypes: ["property_declaration"], + nameField: "name", + bodyField: "body", + paramsField: "parameters", + returnField: "return_type", + classifyClassNode: (node) => { + return node.type === "trait_declaration" ? "trait" : "class"; + }, + getVisibility: (node) => { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child?.type === "visibility_modifier") { + const text = child.text; + if (text === "public") return "public"; + if (text === "private") return "private"; + if (text === "protected") return "protected"; + } + } + return "public"; + }, + isStatic: (node) => { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child?.type === "static_modifier") return true; + } + return false; + }, + visitNode: (node, ctx) => { + if (node.type === "const_declaration") { + const constElements = node.namedChildren.filter((c) => c.type === "const_element"); + for (const elem of constElements) { + const nameNode = elem.namedChildren.find((c) => c.type === "name"); + if (!nameNode) continue; + const name = getNodeText(nameNode, ctx.source); + ctx.createNode("constant", name, elem, {}); + } + return true; + } + if (node.type === "use_declaration") { + const names = node.namedChildren.filter((c) => c.type === "name" || c.type === "qualified_name"); + const parentId = ctx.nodeStack.length > 0 ? ctx.nodeStack[ctx.nodeStack.length - 1] : void 0; + if (parentId) { + for (const nameNode of names) { + const traitName = getNodeText(nameNode, ctx.source); + ctx.addUnresolvedReference({ + fromNodeId: parentId, + referenceName: traitName, + referenceKind: "implements", + filePath: ctx.filePath, + line: node.startPosition.row + 1, + column: node.startPosition.column + }); + } + } + return true; + } + return false; + }, + extractImport: (node, source) => { + const importText = source.substring(node.startIndex, node.endIndex).trim(); + const namespacePrefix = node.namedChildren.find((c) => c.type === "namespace_name"); + const useGroup = node.namedChildren.find((c) => c.type === "namespace_use_group"); + if (namespacePrefix && useGroup) { + return null; + } + const useClause = node.namedChildren.find((c) => c.type === "namespace_use_clause"); + if (useClause) { + const qualifiedName = useClause.namedChildren.find((c) => c.type === "qualified_name"); + if (qualifiedName) { + return { moduleName: getNodeText(qualifiedName, source), signature: importText }; + } + const name = useClause.namedChildren.find((c) => c.type === "name"); + if (name) { + return { moduleName: getNodeText(name, source), signature: importText }; + } + } + return null; + } + }; + } +}); + +// src/extraction/languages/ruby.ts +var rubyExtractor; +var init_ruby = __esm({ + "src/extraction/languages/ruby.ts"() { + "use strict"; + init_tree_sitter_helpers(); + rubyExtractor = { + functionTypes: ["method"], + classTypes: ["class"], + methodTypes: ["method", "singleton_method"], + interfaceTypes: [], + // Ruby uses modules (handled via visitNode hook) + structTypes: [], + enumTypes: [], + typeAliasTypes: [], + importTypes: ["call"], + // require/require_relative + callTypes: ["call", "method_call"], + variableTypes: ["assignment"], + // Ruby uses assignment like Python + nameField: "name", + bodyField: "body", + paramsField: "parameters", + visitNode: (node, ctx) => { + if (node.type !== "module") return false; + const nameNode = node.childForFieldName("name"); + if (!nameNode) return false; + const name = nameNode.text; + const moduleNode = ctx.createNode("module", name, node); + if (!moduleNode) return false; + ctx.pushScope(moduleNode.id); + const body = node.childForFieldName("body"); + if (body) { + for (let i = 0; i < body.namedChildCount; i++) { + const child = body.namedChild(i); + if (child) ctx.visitNode(child); + } + } + ctx.popScope(); + return true; + }, + extractBareCall: (node, _source) => { + if (node.type !== "identifier") return void 0; + const parent = node.parent; + if (!parent) return void 0; + const BLOCK_PARENTS = /* @__PURE__ */ new Set([ + "body_statement", + "then", + "else", + "do", + "begin", + "rescue", + "ensure", + "when" + ]); + if (!BLOCK_PARENTS.has(parent.type)) return void 0; + const name = node.text; + const SKIP = /* @__PURE__ */ new Set([ + "true", + "false", + "nil", + "self", + "super", + "__FILE__", + "__LINE__", + "__dir__" + ]); + if (SKIP.has(name)) return void 0; + if (name.length > 0 && name.charCodeAt(0) >= 65 && name.charCodeAt(0) <= 90) return void 0; + return name; + }, + getVisibility: (node) => { + let sibling = node.previousNamedSibling; + while (sibling) { + if (sibling.type === "call") { + const methodName = getChildByField(sibling, "method"); + if (methodName) { + const text = methodName.text; + if (text === "private") return "private"; + if (text === "protected") return "protected"; + if (text === "public") return "public"; + } + } + sibling = sibling.previousNamedSibling; + } + return "public"; + }, + extractImport: (node, source) => { + const importText = source.substring(node.startIndex, node.endIndex).trim(); + const identifier = node.namedChildren.find((c) => c.type === "identifier"); + if (!identifier) return null; + const methodName = getNodeText(identifier, source); + if (methodName !== "require" && methodName !== "require_relative") { + return null; + } + const argList = node.namedChildren.find((c) => c.type === "argument_list"); + if (argList) { + const stringNode = argList.namedChildren.find((c) => c.type === "string"); + if (stringNode) { + const stringContent = stringNode.namedChildren.find((c) => c.type === "string_content"); + if (stringContent) { + return { moduleName: getNodeText(stringContent, source), signature: importText }; + } + } + } + return null; + } + }; + } +}); + +// src/extraction/languages/swift.ts +var swiftExtractor; +var init_swift = __esm({ + "src/extraction/languages/swift.ts"() { + "use strict"; + init_tree_sitter_helpers(); + swiftExtractor = { + functionTypes: ["function_declaration"], + classTypes: ["class_declaration"], + methodTypes: ["function_declaration"], + // Methods are functions inside classes + interfaceTypes: ["protocol_declaration"], + structTypes: ["struct_declaration"], + enumTypes: ["enum_declaration"], + enumMemberTypes: ["enum_entry"], + typeAliasTypes: ["typealias_declaration"], + importTypes: ["import_declaration"], + callTypes: ["call_expression"], + variableTypes: ["property_declaration", "constant_declaration"], + nameField: "name", + bodyField: "body", + paramsField: "parameter", + returnField: "return_type", + getSignature: (node, source) => { + const params = getChildByField(node, "parameter"); + const returnType = getChildByField(node, "return_type"); + if (!params) return void 0; + let sig = getNodeText(params, source); + if (returnType) { + sig += " -> " + getNodeText(returnType, source); + } + return sig; + }, + getVisibility: (node) => { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child?.type === "modifiers") { + const text = child.text; + if (text.includes("public")) return "public"; + if (text.includes("private")) return "private"; + if (text.includes("internal")) return "internal"; + if (text.includes("fileprivate")) return "private"; + } + } + return "internal"; + }, + isStatic: (node) => { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child?.type === "modifiers") { + if (child.text.includes("static") || child.text.includes("class")) { + return true; + } + } + } + return false; + }, + classifyClassNode: (node) => { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child?.type === "struct") return "struct"; + if (child?.type === "enum") return "enum"; + } + return "class"; + }, + isAsync: (node) => { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child?.type === "modifiers" && child.text.includes("async")) { + return true; + } + } + return false; + }, + extractImport: (node, source) => { + const importText = source.substring(node.startIndex, node.endIndex).trim(); + const identifier = node.namedChildren.find((c) => c.type === "identifier"); + if (identifier) { + return { moduleName: source.substring(identifier.startIndex, identifier.endIndex), signature: importText }; + } + return null; + } + }; + } +}); + +// src/extraction/languages/kotlin.ts +function isFunInterfaceNode(node) { + let hasFun = false; + let hasInterfaceType = false; + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (!child) continue; + if (child.type === "fun" && !child.isNamed) hasFun = true; + if (child.type === "user_type") { + const typeId = child.namedChildren.find((c) => c.type === "type_identifier"); + if (typeId && typeId.text === "interface") hasInterfaceType = true; + } + if (child.type === "ERROR") { + for (let j = 0; j < child.childCount; j++) { + const gc = child.child(j); + if (gc && gc.type === "user_type") { + const typeId = gc.namedChildren.find((c) => c.type === "type_identifier"); + if (typeId && typeId.text === "interface") hasInterfaceType = true; + } + } + } + } + return hasFun && hasInterfaceType; +} +var kotlinExtractor; +var init_kotlin = __esm({ + "src/extraction/languages/kotlin.ts"() { + "use strict"; + init_tree_sitter_helpers(); + kotlinExtractor = { + functionTypes: ["function_declaration"], + classTypes: ["class_declaration"], + methodTypes: ["function_declaration"], + // Methods are functions inside classes + interfaceTypes: [], + // Handled via classifyClassNode + structTypes: [], + // Kotlin uses data classes + enumTypes: [], + // Handled via classifyClassNode + enumMemberTypes: ["enum_entry"], + typeAliasTypes: ["type_alias"], + importTypes: ["import_header"], + callTypes: ["call_expression"], + variableTypes: ["property_declaration"], + fieldTypes: ["property_declaration"], + extraClassNodeTypes: ["object_declaration"], + nameField: "simple_identifier", + bodyField: "function_body", + visitNode: (node, ctx) => { + if (node.type === "lambda_literal") { + const prev = node.previousSibling; + if (prev && prev.type === "ERROR" && isFunInterfaceNode(prev)) return true; + return false; + } + if (node.type !== "ERROR" && node.type !== "function_declaration") return false; + if (node.type === "ERROR") { + const firstChild = node.child(0); + if (firstChild && firstChild.type === "{") return false; + } + if (!isFunInterfaceNode(node)) return false; + let nameText = null; + if (node.type === "function_declaration") { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child && child.type === "ERROR") { + for (let j = 0; j < child.childCount; j++) { + const gc = child.child(j); + if (gc && gc.type === "simple_identifier") { + nameText = gc.text; + break; + } + } + if (nameText) break; + } + } + } + if (!nameText) { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child && child.type === "simple_identifier") { + nameText = child.text; + break; + } + } + } + if (!nameText) return false; + const ifaceNode = ctx.createNode("interface", nameText, node); + if (!ifaceNode) return false; + ctx.pushScope(ifaceNode.id); + if (node.type === "ERROR") { + const nextSibling = node.nextSibling; + if (nextSibling && nextSibling.type === "lambda_literal") { + for (let i = 0; i < nextSibling.namedChildCount; i++) { + const child = nextSibling.namedChild(i); + if (child && child.type === "statements") { + for (let j = 0; j < child.namedChildCount; j++) { + const stmt = child.namedChild(j); + if (stmt) ctx.visitNode(stmt); + } + } + } + } + } + ctx.popScope(); + return true; + }, + paramsField: "function_value_parameters", + returnField: "type", + resolveBody: (node, _bodyField) => { + for (let i = 0; i < node.namedChildCount; i++) { + const child = node.namedChild(i); + if (child && child.type === "ERROR") { + const firstChild = child.child(0); + if (firstChild && firstChild.type === "{") { + return child; + } + } + if (child && (child.type === "function_body" || child.type === "class_body" || child.type === "enum_class_body")) { + return child; + } + } + return null; + }, + classifyClassNode: (node) => { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (!child) continue; + if (child.type === "interface") return "interface"; + if (child.type === "enum") return "enum"; + } + return "class"; + }, + getReceiverType: (node, source) => { + let foundUserType = null; + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (!child) continue; + if (child.type === "user_type") { + foundUserType = child; + } else if (child.type === "." && foundUserType) { + const typeId = foundUserType.namedChildren.find((c) => c.type === "type_identifier"); + return typeId ? getNodeText(typeId, source) : getNodeText(foundUserType, source); + } else if (child.type === "simple_identifier" || child.type === "function_value_parameters") { + break; + } + } + return void 0; + }, + getSignature: (node, source) => { + const params = getChildByField(node, "function_value_parameters"); + const returnType = getChildByField(node, "type"); + if (!params) return void 0; + let sig = getNodeText(params, source); + if (returnType) { + sig += ": " + getNodeText(returnType, source); + } + return sig; + }, + getVisibility: (node) => { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child?.type === "modifiers") { + const text = child.text; + if (text.includes("public")) return "public"; + if (text.includes("private")) return "private"; + if (text.includes("protected")) return "protected"; + if (text.includes("internal")) return "internal"; + } + } + return "public"; + }, + isStatic: (_node) => { + return false; + }, + isAsync: (node) => { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child?.type === "modifiers" && child.text.includes("suspend")) { + return true; + } + } + return false; + }, + extractImport: (node, source) => { + const importText = source.substring(node.startIndex, node.endIndex).trim(); + const identifier = node.namedChildren.find((c) => c.type === "identifier"); + if (identifier) { + return { moduleName: source.substring(identifier.startIndex, identifier.endIndex), signature: importText }; + } + return null; + } + }; + } +}); + +// src/extraction/languages/dart.ts +var dartExtractor; +var init_dart = __esm({ + "src/extraction/languages/dart.ts"() { + "use strict"; + init_tree_sitter_helpers(); + dartExtractor = { + functionTypes: ["function_signature"], + classTypes: ["class_definition"], + methodTypes: ["method_signature"], + interfaceTypes: [], + structTypes: [], + enumTypes: ["enum_declaration"], + enumMemberTypes: ["enum_constant"], + typeAliasTypes: ["type_alias"], + importTypes: ["import_or_export"], + callTypes: [], + // Dart calls use identifier+selector, handled via extractBareCall + variableTypes: [], + extraClassNodeTypes: ["mixin_declaration", "extension_declaration"], + resolveBody: (node, bodyField) => { + if (node.type === "function_signature" || node.type === "method_signature") { + const next = node.nextNamedSibling; + if (next?.type === "function_body") return next; + return null; + } + const standard = node.childForFieldName(bodyField); + if (standard) return standard; + return node.namedChildren.find( + (c) => c.type === "class_body" || c.type === "extension_body" + ) || null; + }, + nameField: "name", + bodyField: "body", + // class_definition uses 'body' field + paramsField: "formal_parameter_list", + returnField: "type", + getSignature: (node, source) => { + let sig = node; + if (node.type === "method_signature") { + const inner = node.namedChildren.find( + (c) => c.type === "function_signature" || c.type === "getter_signature" || c.type === "setter_signature" + ); + if (inner) sig = inner; + } + const params = sig.namedChildren.find((c) => c.type === "formal_parameter_list"); + const retType = sig.namedChildren.find( + (c) => c.type === "type_identifier" || c.type === "void_type" + ); + if (!params && !retType) return void 0; + let result = ""; + if (retType) result += getNodeText(retType, source) + " "; + if (params) result += getNodeText(params, source); + return result.trim() || void 0; + }, + getVisibility: (node) => { + let nameNode = null; + if (node.type === "method_signature") { + const inner = node.namedChildren.find( + (c) => c.type === "function_signature" || c.type === "getter_signature" || c.type === "setter_signature" + ); + if (inner) nameNode = inner.namedChildren.find((c) => c.type === "identifier") || null; + } else { + nameNode = node.childForFieldName("name"); + } + if (nameNode && nameNode.text.startsWith("_")) return "private"; + return "public"; + }, + isAsync: (node) => { + const nextSibling = node.nextNamedSibling; + if (nextSibling?.type === "function_body") { + for (let i = 0; i < nextSibling.childCount; i++) { + const child = nextSibling.child(i); + if (child?.type === "async") return true; + } + } + return false; + }, + isStatic: (node) => { + if (node.type === "method_signature") { + for (let i = 0; i < node.childCount; i++) { + const child = node.child(i); + if (child?.type === "static") return true; + } + } + return false; + }, + extractImport: (node, source) => { + const importText = source.substring(node.startIndex, node.endIndex).trim(); + let moduleName = ""; + const libraryImport = node.namedChildren.find((c) => c.type === "library_import"); + if (libraryImport) { + const importSpec = libraryImport.namedChildren.find((c) => c.type === "import_specification"); + if (importSpec) { + const configurableUri = importSpec.namedChildren.find((c) => c.type === "configurable_uri"); + if (configurableUri) { + const uri = configurableUri.namedChildren.find((c) => c.type === "uri"); + if (uri) { + const stringLiteral = uri.namedChildren.find((c) => c.type === "string_literal"); + if (stringLiteral) { + moduleName = getNodeText(stringLiteral, source).replace(/['"]/g, ""); + } + } + } + } + } + if (!moduleName) { + const libraryExport = node.namedChildren.find((c) => c.type === "library_export"); + if (libraryExport) { + const configurableUri = libraryExport.namedChildren.find((c) => c.type === "configurable_uri"); + if (configurableUri) { + const uri = configurableUri.namedChildren.find((c) => c.type === "uri"); + if (uri) { + const stringLiteral = uri.namedChildren.find((c) => c.type === "string_literal"); + if (stringLiteral) { + moduleName = getNodeText(stringLiteral, source).replace(/['"]/g, ""); + } + } + } + } + } + if (moduleName) { + return { moduleName, signature: importText }; + } + return null; + }, + extractBareCall: (node, _source) => { + if (node.type === "selector") { + const hasArgPart = node.namedChildren.some((c) => c.type === "argument_part"); + if (!hasArgPart) return void 0; + const prev = node.previousNamedSibling; + if (!prev) return void 0; + if (prev.type === "identifier") { + return prev.text; + } + if (prev.type === "selector") { + const accessor = prev.namedChildren.find( + (c) => c.type === "unconditional_assignable_selector" || c.type === "conditional_assignable_selector" + ); + if (accessor) { + const methodId = accessor.namedChildren.find((c) => c.type === "identifier"); + if (methodId) { + const accessorPrev = prev.previousNamedSibling; + if (accessorPrev?.type === "identifier") { + return accessorPrev.text + "." + methodId.text; + } + return methodId.text; + } + } + } + if (prev.type === "unconditional_assignable_selector" || prev.type === "conditional_assignable_selector") { + const methodId = prev.namedChildren.find((c) => c.type === "identifier"); + if (methodId) return methodId.text; + } + return void 0; + } + if (node.type === "new_expression") { + const typeId = node.namedChildren.find((c) => c.type === "type_identifier"); + if (typeId) return typeId.text; + return void 0; + } + if (node.type === "const_object_expression") { + const typeId = node.namedChildren.find((c) => c.type === "type_identifier"); + const nameId = node.namedChildren.find((c) => c.type === "identifier"); + if (typeId && nameId) return typeId.text + "." + nameId.text; + if (typeId) return typeId.text; + return void 0; + } + return void 0; + } + }; + } +}); + +// src/extraction/languages/pascal.ts +var pascalExtractor; +var init_pascal = __esm({ + "src/extraction/languages/pascal.ts"() { + "use strict"; + init_tree_sitter_helpers(); + pascalExtractor = { + functionTypes: ["declProc"], + classTypes: ["declClass"], + methodTypes: ["declProc"], + interfaceTypes: ["declIntf"], + structTypes: [], + enumTypes: ["declEnum"], + typeAliasTypes: ["declType"], + importTypes: ["declUses"], + callTypes: ["exprCall"], + variableTypes: ["declField", "declConst"], + nameField: "name", + bodyField: "body", + paramsField: "args", + returnField: "type", + getSignature: (node, source) => { + const args = getChildByField(node, "args"); + const returnType = node.namedChildren.find( + (c) => c.type === "typeref" + ); + if (!args && !returnType) return void 0; + let sig = ""; + if (args) sig = getNodeText(args, source); + if (returnType) { + sig += ": " + getNodeText(returnType, source); + } + return sig || void 0; + }, + getVisibility: (node) => { + let current = node.parent; + while (current) { + if (current.type === "declSection") { + for (let i = 0; i < current.childCount; i++) { + const child = current.child(i); + if (child?.type === "kPublic" || child?.type === "kPublished") + return "public"; + if (child?.type === "kPrivate") return "private"; + if (child?.type === "kProtected") return "protected"; + } + } + current = current.parent; + } + return void 0; + }, + isExported: (_node, _source) => { + return false; + }, + isStatic: (node) => { + for (let i = 0; i < node.childCount; i++) { + if (node.child(i)?.type === "kClass") return true; + } + return false; + }, + isConst: (node) => { + return node.type === "declConst"; + } + }; + } +}); + +// src/extraction/languages/scala.ts +function getValVarName(node, source) { + const patternNode = node.childForFieldName("pattern"); + if (!patternNode) return null; + if (patternNode.type === "identifier") return getNodeText(patternNode, source); + const identChild = patternNode.namedChildren.find((c) => c.type === "identifier"); + return identChild ? getNodeText(identChild, source) : null; +} +function extractVisibility(node) { + for (let i = 0; i < node.namedChildCount; i++) { + const child = node.namedChild(i); + if (!child) continue; + if (child.type === "modifiers" || child.type === "access_modifier") { + const text = child.text; + if (text.includes("private")) return "private"; + if (text.includes("protected")) return "protected"; + } + } + return "public"; +} +var scalaExtractor; +var init_scala = __esm({ + "src/extraction/languages/scala.ts"() { + "use strict"; + init_tree_sitter_helpers(); + scalaExtractor = { + // top-level function_definition is handled via methodTypes (same pattern as Kotlin) + functionTypes: [], + classTypes: ["class_definition", "object_definition", "trait_definition"], + methodTypes: ["function_definition", "function_declaration"], + interfaceTypes: [], + structTypes: [], + enumTypes: ["enum_definition"], + enumMemberTypes: [], + // handled in visitNode — enum_case_definitions wraps the cases + typeAliasTypes: ["type_definition"], + importTypes: ["import_declaration"], + callTypes: ["call_expression"], + variableTypes: [], + // val/var handled in visitNode (use `pattern` field, not `name`) + fieldTypes: [], + extraClassNodeTypes: [], + nameField: "name", + bodyField: "body", + paramsField: "parameters", + returnField: "return_type", + interfaceKind: "trait", + classifyClassNode: (node) => { + if (node.type === "trait_definition") return "trait"; + return "class"; + }, + getSignature: (node, source) => { + const params = node.childForFieldName("parameters"); + const returnType = node.childForFieldName("return_type"); + if (!params && !returnType) return void 0; + let sig = params ? getNodeText(params, source) : ""; + if (returnType) sig += ": " + getNodeText(returnType, source); + return sig || void 0; + }, + getVisibility: (node) => extractVisibility(node), + isAsync: () => false, + isStatic: (node) => { + for (let i = 0; i < node.namedChildCount; i++) { + const child = node.namedChild(i); + if (child?.type === "modifiers" && child.text.includes("static")) return true; + } + return false; + }, + visitNode: (node, ctx) => { + const t = node.type; + if (t === "val_definition" || t === "var_definition") { + const name = getValVarName(node, ctx.source); + if (!name) return false; + const isInClass = ctx.nodeStack.length > 0 && (() => { + const parentId = ctx.nodeStack[ctx.nodeStack.length - 1]; + const parentNode = ctx.nodes.find((n) => n.id === parentId); + return parentNode != null && (parentNode.kind === "class" || parentNode.kind === "trait" || parentNode.kind === "interface" || parentNode.kind === "struct" || parentNode.kind === "enum" || parentNode.kind === "module"); + })(); + const kind = isInClass ? "field" : t === "val_definition" ? "constant" : "variable"; + const typeNode = node.childForFieldName("type"); + const sig = typeNode ? `${t === "val_definition" ? "val" : "var"} ${name}: ${getNodeText(typeNode, ctx.source)}` : void 0; + ctx.createNode(kind, name, node, { signature: sig, visibility: extractVisibility(node) }); + return true; + } + if (t === "enum_case_definitions") { + for (let i = 0; i < node.namedChildCount; i++) { + const child = node.namedChild(i); + if (!child) continue; + if (child.type === "simple_enum_case" || child.type === "full_enum_case") { + const nameNode = child.childForFieldName("name"); + if (nameNode) ctx.createNode("enum_member", getNodeText(nameNode, ctx.source), child); + } + } + return true; + } + if (t === "extension_definition") { + const body = node.childForFieldName("body"); + if (body) { + for (let i = 0; i < body.namedChildCount; i++) { + const child = body.namedChild(i); + if (child) ctx.visitNode(child); + } + } + return true; + } + return false; + }, + extractImport: (node, source) => { + const importText = getNodeText(node, source).trim(); + const pathNode = node.childForFieldName("path"); + if (pathNode) return { moduleName: getNodeText(pathNode, source), signature: importText }; + for (let i = 0; i < node.namedChildCount; i++) { + const child = node.namedChild(i); + if (child?.type === "identifier" || child?.type === "stable_identifier") { + return { moduleName: getNodeText(child, source), signature: importText }; + } + } + return null; + } + }; + } +}); + +// src/extraction/languages/index.ts +var EXTRACTORS; +var init_languages = __esm({ + "src/extraction/languages/index.ts"() { + "use strict"; + init_typescript(); + init_javascript(); + init_python(); + init_go(); + init_rust(); + init_java(); + init_c_cpp(); + init_csharp(); + init_php(); + init_ruby(); + init_swift(); + init_kotlin(); + init_dart(); + init_pascal(); + init_scala(); + EXTRACTORS = { + typescript: typescriptExtractor, + tsx: typescriptExtractor, + javascript: javascriptExtractor, + jsx: javascriptExtractor, + python: pythonExtractor, + go: goExtractor, + rust: rustExtractor, + java: javaExtractor, + c: cExtractor, + cpp: cppExtractor, + csharp: csharpExtractor, + php: phpExtractor, + ruby: rubyExtractor, + swift: swiftExtractor, + kotlin: kotlinExtractor, + dart: dartExtractor, + pascal: pascalExtractor, + scala: scalaExtractor + }; + } +}); + +// src/extraction/liquid-extractor.ts +var LiquidExtractor; +var init_liquid_extractor = __esm({ + "src/extraction/liquid-extractor.ts"() { + "use strict"; + init_tree_sitter_helpers(); + LiquidExtractor = class { + filePath; + source; + nodes = []; + edges = []; + unresolvedReferences = []; + errors = []; + constructor(filePath, source) { + this.filePath = filePath; + this.source = source; + } + /** + * Extract from Liquid source + */ + extract() { + const startTime = Date.now(); + try { + const fileNode = this.createFileNode(); + this.extractSnippetReferences(fileNode.id); + this.extractSectionReferences(fileNode.id); + this.extractSchema(fileNode.id); + this.extractAssignments(fileNode.id); + } catch (error) { + this.errors.push({ + message: `Liquid extraction error: ${error instanceof Error ? error.message : String(error)}`, + severity: "error", + code: "parse_error" + }); + } + return { + nodes: this.nodes, + edges: this.edges, + unresolvedReferences: this.unresolvedReferences, + errors: this.errors, + durationMs: Date.now() - startTime + }; + } + /** + * Create a file node for the Liquid template + */ + createFileNode() { + const lines = this.source.split("\n"); + const id = generateNodeId(this.filePath, "file", this.filePath, 1); + const fileNode = { + id, + kind: "file", + name: this.filePath.split("/").pop() || this.filePath, + qualifiedName: this.filePath, + filePath: this.filePath, + language: "liquid", + startLine: 1, + endLine: lines.length, + startColumn: 0, + endColumn: lines[lines.length - 1]?.length || 0, + updatedAt: Date.now() + }; + this.nodes.push(fileNode); + return fileNode; + } + /** + * Extract {% render 'snippet' %} and {% include 'snippet' %} references + */ + extractSnippetReferences(fileNodeId) { + const renderRegex = /\{%[-]?\s*(render|include)\s+['"]([^'"]+)['"]/g; + let match; + while ((match = renderRegex.exec(this.source)) !== null) { + const [fullMatch, tagType, snippetName] = match; + const line = this.getLineNumber(match.index); + const importNodeId = generateNodeId(this.filePath, "import", snippetName, line); + const importNode = { + id: importNodeId, + kind: "import", + name: snippetName, + qualifiedName: `${this.filePath}::import:${snippetName}`, + filePath: this.filePath, + language: "liquid", + signature: fullMatch, + startLine: line, + endLine: line, + startColumn: match.index - this.getLineStart(line), + endColumn: match.index - this.getLineStart(line) + fullMatch.length, + updatedAt: Date.now() + }; + this.nodes.push(importNode); + this.edges.push({ + source: fileNodeId, + target: importNodeId, + kind: "contains" + }); + const nodeId = generateNodeId(this.filePath, "component", `${tagType}:${snippetName}`, line); + const node = { + id: nodeId, + kind: "component", + name: snippetName, + qualifiedName: `${this.filePath}::${tagType}:${snippetName}`, + filePath: this.filePath, + language: "liquid", + startLine: line, + endLine: line, + startColumn: match.index - this.getLineStart(line), + endColumn: match.index - this.getLineStart(line) + fullMatch.length, + updatedAt: Date.now() + }; + this.nodes.push(node); + this.edges.push({ + source: fileNodeId, + target: nodeId, + kind: "contains" + }); + this.unresolvedReferences.push({ + fromNodeId: fileNodeId, + referenceName: `snippets/${snippetName}.liquid`, + referenceKind: "references", + line, + column: match.index - this.getLineStart(line) + }); + } + } + /** + * Extract {% section 'name' %} references + */ + extractSectionReferences(fileNodeId) { + const sectionRegex = /\{%[-]?\s*section\s+['"]([^'"]+)['"]/g; + let match; + while ((match = sectionRegex.exec(this.source)) !== null) { + const [fullMatch, sectionName] = match; + const line = this.getLineNumber(match.index); + const importNodeId = generateNodeId(this.filePath, "import", sectionName, line); + const importNode = { + id: importNodeId, + kind: "import", + name: sectionName, + qualifiedName: `${this.filePath}::import:${sectionName}`, + filePath: this.filePath, + language: "liquid", + signature: fullMatch, + startLine: line, + endLine: line, + startColumn: match.index - this.getLineStart(line), + endColumn: match.index - this.getLineStart(line) + fullMatch.length, + updatedAt: Date.now() + }; + this.nodes.push(importNode); + this.edges.push({ + source: fileNodeId, + target: importNodeId, + kind: "contains" + }); + const nodeId = generateNodeId(this.filePath, "component", `section:${sectionName}`, line); + const node = { + id: nodeId, + kind: "component", + name: sectionName, + qualifiedName: `${this.filePath}::section:${sectionName}`, + filePath: this.filePath, + language: "liquid", + startLine: line, + endLine: line, + startColumn: match.index - this.getLineStart(line), + endColumn: match.index - this.getLineStart(line) + fullMatch.length, + updatedAt: Date.now() + }; + this.nodes.push(node); + this.edges.push({ + source: fileNodeId, + target: nodeId, + kind: "contains" + }); + this.unresolvedReferences.push({ + fromNodeId: fileNodeId, + referenceName: `sections/${sectionName}.liquid`, + referenceKind: "references", + line, + column: match.index - this.getLineStart(line) + }); + } + } + /** + * Extract {% schema %}...{% endschema %} blocks + */ + extractSchema(fileNodeId) { + const schemaRegex = /\{%[-]?\s*schema\s*[-]?%\}([\s\S]*?)\{%[-]?\s*endschema\s*[-]?%\}/g; + let match; + while ((match = schemaRegex.exec(this.source)) !== null) { + const [fullMatch, schemaContent] = match; + const startLine = this.getLineNumber(match.index); + const endLine = this.getLineNumber(match.index + fullMatch.length); + let schemaName = "schema"; + try { + const schemaJson = JSON.parse(schemaContent); + if (schemaJson.name) { + schemaName = typeof schemaJson.name === "string" ? schemaJson.name : schemaJson.name.en || Object.values(schemaJson.name)[0] || "schema"; + } + } catch { + } + const nodeId = generateNodeId(this.filePath, "constant", `schema:${schemaName}`, startLine); + const node = { + id: nodeId, + kind: "constant", + name: schemaName, + qualifiedName: `${this.filePath}::schema:${schemaName}`, + filePath: this.filePath, + language: "liquid", + startLine, + endLine, + startColumn: match.index - this.getLineStart(startLine), + endColumn: 0, + docstring: schemaContent?.trim().substring(0, 200), + // Store first 200 chars as docstring + updatedAt: Date.now() + }; + this.nodes.push(node); + this.edges.push({ + source: fileNodeId, + target: nodeId, + kind: "contains" + }); + } + } + /** + * Extract {% assign var = value %} statements + */ + extractAssignments(fileNodeId) { + const assignRegex = /\{%[-]?\s*assign\s+(\w+)\s*=/g; + let match; + while ((match = assignRegex.exec(this.source)) !== null) { + const [, variableName] = match; + const line = this.getLineNumber(match.index); + const nodeId = generateNodeId(this.filePath, "variable", variableName, line); + const node = { + id: nodeId, + kind: "variable", + name: variableName, + qualifiedName: `${this.filePath}::${variableName}`, + filePath: this.filePath, + language: "liquid", + startLine: line, + endLine: line, + startColumn: match.index - this.getLineStart(line), + endColumn: match.index - this.getLineStart(line) + match[0].length, + updatedAt: Date.now() + }; + this.nodes.push(node); + this.edges.push({ + source: fileNodeId, + target: nodeId, + kind: "contains" + }); + } + } + /** + * Get the line number for a character index + */ + getLineNumber(index) { + const substring = this.source.substring(0, index); + return (substring.match(/\n/g) || []).length + 1; + } + /** + * Get the character index of the start of a line + */ + getLineStart(lineNumber) { + const lines = this.source.split("\n"); + let index = 0; + for (let i = 0; i < lineNumber - 1 && i < lines.length; i++) { + index += lines[i].length + 1; + } + return index; + } + }; + } +}); + +// src/extraction/svelte-extractor.ts +var SVELTE_RUNES, SvelteExtractor; +var init_svelte_extractor = __esm({ + "src/extraction/svelte-extractor.ts"() { + "use strict"; + init_tree_sitter_helpers(); + init_tree_sitter(); + init_grammars(); + SVELTE_RUNES = /* @__PURE__ */ new Set([ + "$props", + "$state", + "$derived", + "$effect", + "$bindable", + "$inspect", + "$host", + "$snippet" + ]); + SvelteExtractor = class { + filePath; + source; + nodes = []; + edges = []; + unresolvedReferences = []; + errors = []; + constructor(filePath, source) { + this.filePath = filePath; + this.source = source; + } + /** + * Extract from Svelte source + */ + extract() { + const startTime = Date.now(); + try { + const componentNode = this.createComponentNode(); + const scriptBlocks = this.extractScriptBlocks(); + for (const block of scriptBlocks) { + this.processScriptBlock(block, componentNode.id); + } + this.extractTemplateCalls(componentNode.id, scriptBlocks); + this.extractTemplateComponents(componentNode.id); + this.unresolvedReferences = this.unresolvedReferences.filter( + (ref) => !SVELTE_RUNES.has(ref.referenceName) + ); + } catch (error) { + this.errors.push({ + message: `Svelte extraction error: ${error instanceof Error ? error.message : String(error)}`, + severity: "error", + code: "parse_error" + }); + } + return { + nodes: this.nodes, + edges: this.edges, + unresolvedReferences: this.unresolvedReferences, + errors: this.errors, + durationMs: Date.now() - startTime + }; + } + /** + * Create a component node for the .svelte file + */ + createComponentNode() { + const lines = this.source.split("\n"); + const fileName = this.filePath.split(/[/\\]/).pop() || this.filePath; + const componentName = fileName.replace(/\.svelte$/, ""); + const id = generateNodeId(this.filePath, "component", componentName, 1); + const node = { + id, + kind: "component", + name: componentName, + qualifiedName: `${this.filePath}::${componentName}`, + filePath: this.filePath, + language: "svelte", + startLine: 1, + endLine: lines.length, + startColumn: 0, + endColumn: lines[lines.length - 1]?.length || 0, + isExported: true, + // Svelte components are always importable + updatedAt: Date.now() + }; + this.nodes.push(node); + return node; + } + /** + * Extract and ranges + const tagRegex = /<(script|style)(\s[^>]*)?>[\s\S]*?<\/\1>/g; + let tagMatch; + while ((tagMatch = tagRegex.exec(this.source)) !== null) { + const startLine = (this.source.substring(0, tagMatch.index).match(/\n/g) || []).length; + const endLine = startLine + (tagMatch[0].match(/\n/g) || []).length; + coveredRanges.push([startLine, endLine]); + } + // Find template expressions: {...} outside of script/style blocks + // Matches curly-brace expressions, excluding Svelte block syntax ({#if}, {:else}, {/if}, {@html}, {@render}) + const lines = this.source.split('\n'); + const exprRegex = /\{([^}#/:@][^}]*)\}/g; + for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) { + // Skip lines inside script/style blocks + if (coveredRanges.some(([start, end]) => lineIdx >= start && lineIdx <= end)) + continue; + const line = lines[lineIdx]; + let exprMatch; + while ((exprMatch = exprRegex.exec(line)) !== null) { + const expr = exprMatch[1]; + // Extract function calls: identifiers followed by ( + // Matches: cn(...), buttonVariants(...), obj.method(...) + const callRegex = /\b([a-zA-Z_$][\w$.]*)\s*\(/g; + let callMatch; + while ((callMatch = callRegex.exec(expr)) !== null) { + const calleeName = callMatch[1]; + // Skip Svelte runes, control flow keywords, and common non-function patterns + if (SVELTE_RUNES.has(calleeName)) + continue; + if (calleeName === 'if' || calleeName === 'else' || calleeName === 'each' || calleeName === 'await') + continue; + this.unresolvedReferences.push({ + fromNodeId: componentNodeId, + referenceName: calleeName, + referenceKind: 'calls', + line: lineIdx + 1, // 1-indexed + column: exprMatch.index + callMatch.index, + filePath: this.filePath, + language: 'svelte', + }); + } + } + } + } + /** + * Extract component usages from the Svelte template. + * + * PascalCase tags like ,