From 9d27ecd8a64b9288ecaa2321b0b6d178299d96fb Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Mon, 29 Dec 2025 17:37:37 +0900 Subject: [PATCH 01/57] docs: add CLAUDE.md --- CLAUDE.md | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..8c1dfd7 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,74 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +`@cawpea/coderef` is a tool for validating and auto-fixing code references in markdown documentation. It ensures code snippets in documentation stay synchronized with actual source code through CODE_REF comments and AST-based symbol searching. + +## Development Commands + +### Building +```bash +npm run build # Build for production (CJS + ESM + types) +npm run dev # Watch mode for development +``` + +### Testing +```bash +npm test # Run all tests +npm run test:watch # Run tests in watch mode +npm run test:coverage # Run tests with coverage report +``` + +Coverage thresholds are set to 80% for all metrics (branches, functions, lines, statements). CLI code is excluded from coverage as it's verified through integration tests. + +### Linting & Formatting +```bash +npm run lint # Lint TypeScript files +npm run lint:fix # Auto-fix linting issues +npm run format # Format code with Prettier +npm run format:check # Check formatting without modifying +npm run type-check # Run TypeScript compiler checks +``` + +### Single Test Execution +```bash +npx jest # Run specific test file +npx jest -t "" # Run tests matching pattern +``` + +## Architecture + +### Build System +- **tsup**: Generates both CJS and ESM formats with type declarations +- Output directory: `./dist` +- Entry points defined in package.json exports for proper dual-package support + +### Module Structure +The project is organized into three main directories under `src/`: +- `cli/`: Command-line interface implementations (validate.ts, fix.ts) +- `core/`: Core validation and fixing logic +- `utils/`: Shared utility functions + +### CODE_REF Syntax Patterns +The tool supports three reference patterns: + +1. **Line-based references**: `` +2. **Symbol references**: `` +3. **Class method references**: `` + +### AST Parsing +Uses `@typescript-eslint/typescript-estree` for TypeScript/JavaScript symbol searching and code extraction. + +### Test Configuration +- Test files: `test/**/*.test.ts` +- Path alias: `@/` maps to `src/` +- Environment: Node.js +- Preset: ts-jest + +## Publishing +The `prepublishOnly` script ensures both build and tests pass before publishing to npm. + +## Node Version +Minimum Node.js version: 16.0.0 From 3783097aba096effa5e51043c7f24a113c1b467f Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Mon, 29 Dec 2025 20:43:46 +0900 Subject: [PATCH 02/57] docs: add git commit rules to CLAUDE.md --- .claude/settings.local.json | 11 +++++++++++ CLAUDE.md | 29 +++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..524551e --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,11 @@ +{ + "permissions": { + "allow": [ + "Bash(tree:*)", + "Bash(find:*)", + "Bash(cat:*)", + "Bash(git ls-tree:*)", + "Bash(ls:*)" + ] + } +} diff --git a/CLAUDE.md b/CLAUDE.md index 8c1dfd7..750e654 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -38,6 +38,35 @@ npx jest # Run specific test file npx jest -t "" # Run tests matching pattern ``` +## Git Commit Message Convention + +This project follows [Conventional Commits](https://www.conventionalcommits.org/) specification for commit messages. + +### Commit Message Format +``` +: + +[optional body] + +[optional footer] +``` + +### Commit Types + +| Type | Description | Semantic Version Impact | Examples | +| ---------- | -------------------------------------------------------------- | ----------------------- | ------------------------------------------ | +| `feat` | New feature | MINOR (1.x.0) | Add evaluation agent, new UI component | +| `fix` | Bug fix | PATCH (1.0.x) | Fix contrast ratio calculation error | +| `docs` | Documentation only changes | None | Update README, add comments | +| `style` | Changes that don't affect code meaning (whitespace, formatting)| None | Run Prettier, fix indentation | +| `refactor` | Code changes that neither fix bugs nor add features | None | Split function, rename variables | +| `test` | Adding or updating tests | None | Add unit tests, improve mocks | +| `chore` | Changes to build process or tools | None | Update dependencies, modify config files | +| `ci` | Changes to CI configuration files and scripts | None | Update GitHub Actions | +| `perf` | Performance improvements | PATCH (1.0.x) | Optimize API response time | +| `build` | Changes to build system or external dependencies | None | Modify Webpack config, add npm scripts | +| `revert` | Revert a previous commit | Depends on original | Revert previous commit | + ## Architecture ### Build System From ca6ab420510bcb12988b41cde0db982de199afe3 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Mon, 29 Dec 2025 20:45:17 +0900 Subject: [PATCH 03/57] chore: add .node-version --- .node-version | 1 + 1 file changed, 1 insertion(+) create mode 100644 .node-version diff --git a/.node-version b/.node-version new file mode 100644 index 0000000..3a6161c --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +22.19.0 \ No newline at end of file From 43bf006df05e2fbf31301ed081c9807a5b3c2a60 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Mon, 29 Dec 2025 20:57:09 +0900 Subject: [PATCH 04/57] feat: implement configuration system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add comprehensive configuration system with support for: - .coderefrc.json file - package.json "coderef" field - Environment variables (CODEREF_*) - Programmatic options with proper precedence Includes helper functions for path resolution and 32 comprehensive tests with 100% statement/function/line coverage and 97.14% branch coverage. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/config.ts | 294 ++++++++++++++++++++++++++++++++++ test/config.test.ts | 377 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 671 insertions(+) create mode 100644 src/config.ts create mode 100644 test/config.test.ts diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..91bbbb9 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,294 @@ +/** + * Configuration system for @cawpea/coderef + * + * Supports loading configuration from: + * - .coderefrc.json file + * - package.json "coderef" field + * - Environment variables (CODEREF_*) + * - Programmatic options + */ + +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * Base configuration for CODE_REF validation + */ +export interface CodeRefConfig { + /** + * Project root directory (default: process.cwd()) + */ + projectRoot: string; + + /** + * Documentation directory relative to projectRoot (default: "docs") + */ + docsDir: string; + + /** + * Path to ignore file relative to projectRoot (default: ".docsignore") + */ + ignoreFile?: string; + + /** + * Additional ignore patterns (globs) + */ + ignorePatterns?: string[]; + + /** + * Enable verbose logging + */ + verbose?: boolean; + + /** + * Specific files or directories to validate (relative to docsDir) + */ + targets?: string[]; +} + +/** + * Configuration for CODE_REF fix command + */ +export interface CodeRefFixConfig extends CodeRefConfig { + /** + * Dry-run mode: show what would be fixed without modifying files + */ + dryRun?: boolean; + + /** + * Auto-fix mode: apply fixes without prompting + */ + auto?: boolean; + + /** + * Create backup before fixing (default: true) + */ + backup?: boolean; +} + +/** + * Partial configuration that can be provided by users + */ +export type PartialCodeRefConfig = Partial; +export type PartialCodeRefFixConfig = Partial; + +/** + * Get default configuration values + * Note: Function to ensure process.cwd() is evaluated at call time + */ +function getDefaultConfig(): CodeRefConfig { + return { + projectRoot: process.cwd(), + docsDir: 'docs', + ignoreFile: '.docsignore', + verbose: false, + }; +} + +/** + * Load configuration from .coderefrc.json file + */ +function loadConfigFile(projectRoot: string): PartialCodeRefConfig | null { + const configPath = path.join(projectRoot, '.coderefrc.json'); + + if (!fs.existsSync(configPath)) { + return null; + } + + try { + const content = fs.readFileSync(configPath, 'utf-8'); + return JSON.parse(content); + } catch (error) { + throw new Error( + `Failed to load .coderefrc.json: ${error instanceof Error ? error.message : String(error)}` + ); + } +} + +/** + * Load configuration from package.json "coderef" field + */ +function loadPackageJsonConfig(projectRoot: string): PartialCodeRefConfig | null { + const packageJsonPath = path.join(projectRoot, 'package.json'); + + if (!fs.existsSync(packageJsonPath)) { + return null; + } + + try { + const content = fs.readFileSync(packageJsonPath, 'utf-8'); + const packageJson = JSON.parse(content); + return packageJson.coderef || null; + } catch (error) { + // Silently ignore package.json parsing errors + return null; + } +} + +/** + * Load configuration from environment variables + */ +function loadEnvConfig(): PartialCodeRefConfig { + const config: PartialCodeRefConfig = {}; + + if (process.env.CODEREF_PROJECT_ROOT) { + config.projectRoot = process.env.CODEREF_PROJECT_ROOT; + } + + if (process.env.CODEREF_DOCS_DIR) { + config.docsDir = process.env.CODEREF_DOCS_DIR; + } + + if (process.env.CODEREF_IGNORE_FILE) { + config.ignoreFile = process.env.CODEREF_IGNORE_FILE; + } + + if (process.env.CODEREF_VERBOSE) { + config.verbose = process.env.CODEREF_VERBOSE === 'true'; + } + + return config; +} + +/** + * Merge configuration objects with proper precedence + * + * Precedence (highest to lowest): + * 1. Programmatic options + * 2. Environment variables + * 3. .coderefrc.json + * 4. package.json "coderef" field + * 5. Default values + */ +function mergeConfigs( + defaultConfig: CodeRefConfig, + ...configs: Array +): CodeRefConfig { + const merged: Partial = {}; + + for (const config of configs) { + if (config) { + Object.assign(merged, config); + } + } + + return { + ...defaultConfig, + ...merged, + } as CodeRefConfig; +} + +/** + * Validate configuration values + */ +function validateConfig(config: CodeRefConfig): void { + // Ensure projectRoot is an absolute path + if (!path.isAbsolute(config.projectRoot)) { + config.projectRoot = path.resolve(config.projectRoot); + } + + // Validate projectRoot exists + if (!fs.existsSync(config.projectRoot)) { + throw new Error(`Project root does not exist: ${config.projectRoot}`); + } + + // Validate docsDir (it's okay if it doesn't exist yet) + if (!config.docsDir || config.docsDir.trim() === '') { + throw new Error('docsDir cannot be empty'); + } + + // Ensure docsDir is not absolute + if (path.isAbsolute(config.docsDir)) { + throw new Error('docsDir must be a relative path'); + } +} + +/** + * Load and merge configuration from all sources + * + * @param options - Programmatic configuration options + * @returns Complete configuration object + */ +export function loadConfig(options: PartialCodeRefConfig = {}): CodeRefConfig { + // Get default configuration (evaluated at call time) + const defaultConfig = getDefaultConfig(); + + // Determine projectRoot first (needed for loading config files) + const projectRoot = options.projectRoot || process.env.CODEREF_PROJECT_ROOT || process.cwd(); + + // Load from all sources + const packageJsonConfig = loadPackageJsonConfig(projectRoot); + const fileConfig = loadConfigFile(projectRoot); + const envConfig = loadEnvConfig(); + + // Merge with proper precedence + const config = mergeConfigs( + defaultConfig, + packageJsonConfig, + fileConfig, + envConfig, + options + ); + + // Validate final configuration + validateConfig(config); + + return config; +} + +/** + * Load configuration for fix command + * + * @param options - Programmatic configuration options for fix + * @returns Complete fix configuration object + */ +export function loadFixConfig(options: PartialCodeRefFixConfig = {}): CodeRefFixConfig { + const baseConfig = loadConfig(options); + + // Fix-specific defaults + const fixDefaults = { + dryRun: false, + auto: false, + backup: true, + }; + + return { + ...baseConfig, + ...fixDefaults, + ...options, + }; +} + +/** + * Resolve a path relative to projectRoot + * + * @param config - Configuration object + * @param relativePath - Path relative to projectRoot + * @returns Absolute path + */ +export function resolveProjectPath(config: CodeRefConfig, relativePath: string): string { + return path.join(config.projectRoot, relativePath); +} + +/** + * Get absolute path to docs directory + * + * @param config - Configuration object + * @returns Absolute path to docs directory + */ +export function getDocsPath(config: CodeRefConfig): string { + return resolveProjectPath(config, config.docsDir); +} + +/** + * Get absolute path to ignore file (if configured) + * + * @param config - Configuration object + * @returns Absolute path to ignore file, or null if not configured + */ +export function getIgnoreFilePath(config: CodeRefConfig): string | null { + if (!config.ignoreFile) { + return null; + } + return resolveProjectPath(config, config.ignoreFile); +} diff --git a/test/config.test.ts b/test/config.test.ts new file mode 100644 index 0000000..daaba2d --- /dev/null +++ b/test/config.test.ts @@ -0,0 +1,377 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { + loadConfig, + loadFixConfig, + resolveProjectPath, + getDocsPath, + getIgnoreFilePath, + type CodeRefConfig, +} from '../src/config'; + +describe('Config System', () => { + const originalCwd = process.cwd(); + const originalEnv = { ...process.env }; + let testDir: string; + + beforeEach(() => { + // Create temporary test directory + testDir = fs.mkdtempSync(path.join(__dirname, 'tmp-config-test-')); + process.chdir(testDir); + }); + + afterEach(() => { + // Restore original state + process.chdir(originalCwd); + process.env = { ...originalEnv }; + + // Clean up test directory + if (fs.existsSync(testDir)) { + fs.rmSync(testDir, { recursive: true, force: true }); + } + }); + + describe('loadConfig', () => { + describe('Default configuration', () => { + it('should return default values when no config files exist', () => { + const config = loadConfig(); + + expect(config).toEqual({ + projectRoot: testDir, + docsDir: 'docs', + ignoreFile: '.docsignore', + verbose: false, + }); + }); + + it('should use process.cwd() as default projectRoot', () => { + const config = loadConfig(); + expect(config.projectRoot).toBe(testDir); + }); + }); + + describe('.coderefrc.json support', () => { + it('should load configuration from .coderefrc.json', () => { + const configContent = { + docsDir: 'documentation', + ignoreFile: '.docignore', + verbose: true, + }; + + fs.writeFileSync( + path.join(testDir, '.coderefrc.json'), + JSON.stringify(configContent, null, 2) + ); + + const config = loadConfig(); + + expect(config.docsDir).toBe('documentation'); + expect(config.ignoreFile).toBe('.docignore'); + expect(config.verbose).toBe(true); + }); + + it('should throw error for invalid .coderefrc.json', () => { + fs.writeFileSync(path.join(testDir, '.coderefrc.json'), 'invalid json{'); + + expect(() => loadConfig()).toThrow('Failed to load .coderefrc.json'); + }); + }); + + describe('package.json support', () => { + it('should load configuration from package.json "coderef" field', () => { + const packageJson = { + name: 'test-package', + version: '1.0.0', + coderef: { + docsDir: 'doc', + verbose: true, + }, + }; + + fs.writeFileSync( + path.join(testDir, 'package.json'), + JSON.stringify(packageJson, null, 2) + ); + + const config = loadConfig(); + + expect(config.docsDir).toBe('doc'); + expect(config.verbose).toBe(true); + }); + + it('should ignore package.json without coderef field', () => { + const packageJson = { + name: 'test-package', + version: '1.0.0', + }; + + fs.writeFileSync( + path.join(testDir, 'package.json'), + JSON.stringify(packageJson, null, 2) + ); + + const config = loadConfig(); + + expect(config.docsDir).toBe('docs'); // default value + }); + + it('should silently ignore invalid package.json', () => { + fs.writeFileSync(path.join(testDir, 'package.json'), 'invalid json{'); + + const config = loadConfig(); + + expect(config.docsDir).toBe('docs'); // default value + }); + }); + + describe('Environment variable support', () => { + it('should load configuration from environment variables', () => { + process.env.CODEREF_DOCS_DIR = 'envdocs'; + process.env.CODEREF_IGNORE_FILE = '.envignore'; + process.env.CODEREF_VERBOSE = 'true'; + + const config = loadConfig(); + + expect(config.docsDir).toBe('envdocs'); + expect(config.ignoreFile).toBe('.envignore'); + expect(config.verbose).toBe(true); + }); + + it('should handle CODEREF_PROJECT_ROOT', () => { + const customRoot = path.join(testDir, 'custom'); + fs.mkdirSync(customRoot); + process.env.CODEREF_PROJECT_ROOT = customRoot; + + const config = loadConfig(); + + expect(config.projectRoot).toBe(customRoot); + }); + + it('should parse verbose as boolean', () => { + process.env.CODEREF_VERBOSE = 'false'; + const config = loadConfig(); + expect(config.verbose).toBe(false); + }); + }); + + describe('Configuration precedence', () => { + beforeEach(() => { + // Set up all config sources + fs.writeFileSync( + path.join(testDir, 'package.json'), + JSON.stringify({ + name: 'test', + coderef: { docsDir: 'package-docs', verbose: false }, + }) + ); + + fs.writeFileSync( + path.join(testDir, '.coderefrc.json'), + JSON.stringify({ docsDir: 'rc-docs', ignoreFile: '.rcignore' }) + ); + + process.env.CODEREF_DOCS_DIR = 'env-docs'; + }); + + it('should prioritize programmatic options over all other sources', () => { + const config = loadConfig({ docsDir: 'programmatic-docs' }); + expect(config.docsDir).toBe('programmatic-docs'); + }); + + it('should prioritize env vars over config files', () => { + const config = loadConfig(); + expect(config.docsDir).toBe('env-docs'); + }); + + it('should prioritize .coderefrc.json over package.json', () => { + delete process.env.CODEREF_DOCS_DIR; + const config = loadConfig(); + expect(config.docsDir).toBe('rc-docs'); + expect(config.ignoreFile).toBe('.rcignore'); + }); + + it('should use package.json when higher priority sources dont specify a field', () => { + delete process.env.CODEREF_DOCS_DIR; + delete process.env.CODEREF_VERBOSE; + + fs.writeFileSync( + path.join(testDir, '.coderefrc.json'), + JSON.stringify({ ignoreFile: '.rcignore' }) + ); + + const config = loadConfig(); + expect(config.verbose).toBe(false); // from package.json + }); + }); + + describe('Programmatic options', () => { + it('should accept programmatic options', () => { + const config = loadConfig({ + projectRoot: testDir, + docsDir: 'custom-docs', + verbose: true, + targets: ['file1.md', 'file2.md'], + }); + + expect(config.docsDir).toBe('custom-docs'); + expect(config.verbose).toBe(true); + expect(config.targets).toEqual(['file1.md', 'file2.md']); + }); + + it('should merge programmatic options with defaults', () => { + const config = loadConfig({ + docsDir: 'custom-docs', + }); + + expect(config.docsDir).toBe('custom-docs'); + expect(config.projectRoot).toBe(testDir); + expect(config.ignoreFile).toBe('.docsignore'); + }); + }); + + describe('Configuration validation', () => { + it('should resolve relative projectRoot to absolute path', () => { + const config = loadConfig({ projectRoot: '.' }); + expect(path.isAbsolute(config.projectRoot)).toBe(true); + }); + + it('should throw error if projectRoot does not exist', () => { + const nonExistentPath = path.join(testDir, 'nonexistent'); + + expect(() => loadConfig({ projectRoot: nonExistentPath })).toThrow( + 'Project root does not exist' + ); + }); + + it('should throw error if docsDir is empty', () => { + expect(() => loadConfig({ docsDir: '' })).toThrow('docsDir cannot be empty'); + }); + + it('should throw error if docsDir is absolute path', () => { + expect(() => loadConfig({ docsDir: '/absolute/path' })).toThrow( + 'docsDir must be a relative path' + ); + }); + }); + }); + + describe('loadFixConfig', () => { + it('should return fix-specific defaults', () => { + const config = loadFixConfig(); + + expect(config.dryRun).toBe(false); + expect(config.auto).toBe(false); + expect(config.backup).toBe(true); + }); + + it('should merge fix options with base config', () => { + const config = loadFixConfig({ + docsDir: 'custom-docs', + dryRun: true, + auto: true, + backup: false, + }); + + expect(config.docsDir).toBe('custom-docs'); + expect(config.dryRun).toBe(true); + expect(config.auto).toBe(true); + expect(config.backup).toBe(false); + }); + + it('should inherit all base configuration options', () => { + fs.writeFileSync( + path.join(testDir, '.coderefrc.json'), + JSON.stringify({ docsDir: 'documentation', verbose: true }) + ); + + const config = loadFixConfig({ dryRun: true }); + + expect(config.docsDir).toBe('documentation'); + expect(config.verbose).toBe(true); + expect(config.dryRun).toBe(true); + }); + }); + + describe('Helper functions', () => { + let config: CodeRefConfig; + + beforeEach(() => { + config = loadConfig(); + }); + + describe('resolveProjectPath', () => { + it('should resolve relative path to absolute path', () => { + const resolved = resolveProjectPath(config, 'src/index.ts'); + expect(resolved).toBe(path.join(testDir, 'src/index.ts')); + }); + + it('should handle paths with ..', () => { + const resolved = resolveProjectPath(config, '../parent/file.ts'); + expect(resolved).toBe(path.join(testDir, '../parent/file.ts')); + }); + }); + + describe('getDocsPath', () => { + it('should return absolute path to docs directory', () => { + const docsPath = getDocsPath(config); + expect(docsPath).toBe(path.join(testDir, 'docs')); + }); + + it('should use configured docsDir', () => { + const customConfig = loadConfig({ docsDir: 'documentation' }); + const docsPath = getDocsPath(customConfig); + expect(docsPath).toBe(path.join(testDir, 'documentation')); + }); + }); + + describe('getIgnoreFilePath', () => { + it('should return absolute path to ignore file when configured', () => { + const ignorePath = getIgnoreFilePath(config); + expect(ignorePath).toBe(path.join(testDir, '.docsignore')); + }); + + it('should return null when ignoreFile is not configured', () => { + const customConfig = loadConfig({ ignoreFile: undefined }); + const ignorePath = getIgnoreFilePath(customConfig); + expect(ignorePath).toBe(null); + }); + + it('should use custom ignoreFile path', () => { + const customConfig = loadConfig({ ignoreFile: '.customignore' }); + const ignorePath = getIgnoreFilePath(customConfig); + expect(ignorePath).toBe(path.join(testDir, '.customignore')); + }); + }); + }); + + describe('Real-world scenarios', () => { + it('should work with a typical project setup', () => { + const docsDir = path.join(testDir, 'docs'); + fs.mkdirSync(docsDir); + fs.writeFileSync(path.join(docsDir, 'README.md'), '# Documentation'); + fs.writeFileSync(path.join(testDir, '.docsignore'), '*.tmp.md'); + + const config = loadConfig(); + + expect(config.projectRoot).toBe(testDir); + expect(getDocsPath(config)).toBe(docsDir); + expect(getIgnoreFilePath(config)).toBe(path.join(testDir, '.docsignore')); + }); + + it('should handle monorepo structure with custom docsDir', () => { + const packagesDir = path.join(testDir, 'packages/mypackage'); + fs.mkdirSync(packagesDir, { recursive: true }); + + fs.writeFileSync( + path.join(packagesDir, '.coderefrc.json'), + JSON.stringify({ docsDir: '../../docs' }) + ); + + process.chdir(packagesDir); + const config = loadConfig(); + + expect(config.docsDir).toBe('../../docs'); + expect(getDocsPath(config)).toBe(path.join(packagesDir, '../../docs')); + }); + }); +}); From 19efe8caafc4396054ab4d37ebe79d25a9c21846 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Mon, 29 Dec 2025 21:11:28 +0900 Subject: [PATCH 05/57] feat: migrate core logic and utils from figma-a11y-reviewer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrate all coderef functionality to the new package structure: - Copy all utils files from scripts/coderef/utils/ to src/utils/ - Copy validate.ts and fix.ts to src/core/ - Move all test files to test/ directory - Update import paths throughout the codebase All 202 tests passing successfully. Next steps: Integrate with configuration system and separate CLI logic. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/core/fix.ts | 267 ++++++++++ src/core/validate.ts | 625 ++++++++++++++++++++++ src/utils/ast-scope-expansion.ts | 306 +++++++++++ src/utils/ast-symbol-search.ts | 265 ++++++++++ src/utils/backup.ts | 57 ++ src/utils/code-comparison.ts | 141 +++++ src/utils/code-ellipsis.ts | 230 ++++++++ src/utils/diff-display.ts | 125 +++++ src/utils/fix.ts | 660 +++++++++++++++++++++++ src/utils/ignore-pattern.ts | 66 +++ src/utils/markdown-edit.ts | 190 +++++++ src/utils/markdown.ts | 105 ++++ src/utils/prompt.ts | 191 +++++++ src/utils/types.ts | 143 +++++ test/ast-symbol-search.test.ts | 311 +++++++++++ test/code-comparison.test.ts | 278 ++++++++++ test/diff-display.test.ts | 120 +++++ test/fix.test.ts | 863 +++++++++++++++++++++++++++++++ test/ignore-pattern.test.ts | 232 +++++++++ test/markdown.test.ts | 373 +++++++++++++ test/validate.test.ts | 804 ++++++++++++++++++++++++++++ 21 files changed, 6352 insertions(+) create mode 100644 src/core/fix.ts create mode 100644 src/core/validate.ts create mode 100644 src/utils/ast-scope-expansion.ts create mode 100644 src/utils/ast-symbol-search.ts create mode 100644 src/utils/backup.ts create mode 100644 src/utils/code-comparison.ts create mode 100644 src/utils/code-ellipsis.ts create mode 100644 src/utils/diff-display.ts create mode 100644 src/utils/fix.ts create mode 100644 src/utils/ignore-pattern.ts create mode 100644 src/utils/markdown-edit.ts create mode 100644 src/utils/markdown.ts create mode 100644 src/utils/prompt.ts create mode 100644 src/utils/types.ts create mode 100644 test/ast-symbol-search.test.ts create mode 100644 test/code-comparison.test.ts create mode 100644 test/diff-display.test.ts create mode 100644 test/fix.test.ts create mode 100644 test/ignore-pattern.test.ts create mode 100644 test/markdown.test.ts create mode 100644 test/validate.test.ts diff --git a/src/core/fix.ts b/src/core/fix.ts new file mode 100644 index 0000000..c5e553e --- /dev/null +++ b/src/core/fix.ts @@ -0,0 +1,267 @@ +#!/usr/bin/env tsx + +/** + * validate:docs:codeで検出されたエラーを対話的に修正するスクリプト + * + * 使用方法: + * tsx scripts/coderef/fix.ts # デフォルト: バックアップなし + * tsx scripts/coderef/fix.ts --dry-run + * tsx scripts/coderef/fix.ts --auto --backup # バックアップを作成する場合 + * npm run coderef:fix + */ + +import * as fs from 'fs'; +import * as path from 'path'; + +import { createBackup } from '../utils/backup'; +import { applyFix, createFixAction, handleMultipleMatches, isFixableError } from '../utils/fix'; +import { askYesNo, createPromptInterface, displayFixPreview } from '../utils/prompt'; +import type { CodeRefError, FixOptions, FixResult } from '../utils/types'; +import { extractCodeRefs, findMarkdownFiles, validateCodeRef } from './validate'; + +// 設定 +const DOCS_DIR = path.join(__dirname, '../..', 'docs'); +const PROJECT_ROOT = path.join(__dirname, '../..'); + +// コマンドライン引数のパース +function parseArgs(): FixOptions { + const args = process.argv.slice(2); + + return { + dryRun: args.includes('--dry-run'), + auto: args.includes('--auto'), + noBackup: !args.includes('--backup'), // デフォルトでバックアップなし(--backupで有効化) + verbose: args.includes('--verbose') || args.includes('-v'), + }; +} + +/** + * グループ化されたエラー + */ +interface ErrorGroup { + docFile: string; + errors: CodeRefError[]; +} + +/** + * エラーを収集 + */ +function collectErrors(): ErrorGroup[] { + const markdownFiles = findMarkdownFiles(DOCS_DIR); + const errorsByDoc: Record = {}; + + for (const file of markdownFiles) { + const content = fs.readFileSync(file, 'utf-8'); + const refs = extractCodeRefs(content, file); + + for (const ref of refs) { + const errors = validateCodeRef(ref); + const fixableErrors = errors.filter(isFixableError); + + if (fixableErrors.length > 0) { + if (!errorsByDoc[file]) { + errorsByDoc[file] = []; + } + errorsByDoc[file].push(...fixableErrors); + } + } + } + + return Object.entries(errorsByDoc).map(([docFile, errors]) => ({ + docFile, + errors, + })); +} + +/** + * メイン処理 + */ +async function main(): Promise { + const options = parseArgs(); + + console.log('🔧 CODE_REFエラーの修正を開始します...\n'); + + if (options.dryRun) { + console.log('⚠️ DRY RUNモード: 実際の変更は行いません\n'); + } + + // エラーを収集 + const errorGroups = collectErrors(); + + if (errorGroups.length === 0) { + console.log('✅ 修正可能なエラーは見つかりませんでした'); + process.exit(0); + } + + // 統計情報 + const totalErrors = errorGroups.reduce((sum, g) => sum + g.errors.length, 0); + console.log(`📊 ${errorGroups.length}個のファイルで${totalErrors}個の修正可能なエラーを検出\n`); + + // 対話インターフェース + const rl = createPromptInterface(); + const fixResults: FixResult[] = []; + const backupFiles = new Set(); + + try { + for (const group of errorGroups) { + console.log(`\n📄 ${path.relative(PROJECT_ROOT, group.docFile)}`); + console.log(` ${group.errors.length}個のエラー\n`); + + // エラーをdocLineNumber降順(下から上へ)にソート + // 下部の修正が上部の行番号に影響を与えないようにするため + const sortedErrors = group.errors.sort((a, b) => { + const lineA = a.ref.docLineNumber ?? Infinity; + const lineB = b.ref.docLineNumber ?? Infinity; + return lineB - lineA; // 降順 + }); + + let _lineOffset = 0; // 累積オフセットを追跡(将来のエッジケース対応用) + + for (const error of sortedErrors) { + console.log(`\n❌ ${error.type}: ${error.message}`); + console.log( + ` 参照: ${path.relative(PROJECT_ROOT, error.ref.docFile)}${error.ref.docLineNumber ? `:${error.ref.docLineNumber}` : ''}` + ); + + // 修正アクションを作成 + let action; + + if (error.type === 'CODE_LOCATION_MISMATCH') { + // 複数マッチの処理 + action = await handleMultipleMatches(error, rl); + } else { + const fixActionResult = await createFixAction(error, rl); + + // 複数のオプションがある場合、ユーザーに選択させる + if (Array.isArray(fixActionResult)) { + console.log('\n🛠️ 修正方法を選択してください:\n'); + + fixActionResult.forEach((opt, index) => { + console.log(` ${index + 1}. ${opt.description}`); + const previewLines = opt.preview.split('\n'); + previewLines.forEach((line) => { + console.log(` ${line}`); + }); + console.log(''); + }); + + if (options.auto) { + // autoモードの場合は最初のオプションを自動選択 + console.log(' ℹ️ autoモードのため、オプション1を自動選択します\n'); + action = fixActionResult[0]; + } else { + // ユーザーに選択させる + const selection = await new Promise((resolve) => { + rl.question( + `修正方法を選択してください (1-${fixActionResult.length}): `, + (answer) => { + const num = parseInt(answer, 10); + if (num >= 1 && num <= fixActionResult.length) { + resolve(num - 1); + } else { + console.log(' ⚠️ 無効な選択です。スキップします。'); + resolve(-1); + } + } + ); + }); + + if (selection === -1) { + console.log(' ⏭️ スキップしました'); + continue; + } + + action = fixActionResult[selection]; + } + } else { + action = fixActionResult; + } + } + + if (!action) { + console.log(' ⚠️ このエラーは修正できません'); + continue; + } + + // プレビュー表示(単一オプションの場合のみ) + if (!Array.isArray(action)) { + displayFixPreview(action); + } + + // 確認 + let shouldFix = options.auto; + if (!options.auto) { + shouldFix = await askYesNo(rl, '\nこの修正を適用しますか?', false); + } + + if (!shouldFix) { + console.log(' ⏭️ スキップしました'); + continue; + } + + // Dry runチェック + if (options.dryRun) { + console.log(' ✅ [DRY RUN] 修正をシミュレートしました'); + fixResults.push({ success: true, action }); + continue; + } + + // バックアップ作成(ファイルごとに1回のみ) + let backupPath: string | undefined; + if (!options.noBackup && !backupFiles.has(group.docFile)) { + backupPath = createBackup(group.docFile); + backupFiles.add(group.docFile); + console.log(` 💾 バックアップ作成: ${path.basename(backupPath)}`); + } + + // 修正を適用 + try { + const lineDelta = applyFix(action); + _lineOffset += lineDelta; // オフセットを累積 + + // デバッグ用のログ + if (lineDelta !== 0) { + console.log(` 📊 Line delta: ${lineDelta > 0 ? '+' : ''}${lineDelta}`); + } + + console.log(' ✅ 修正を適用しました'); + fixResults.push({ success: true, action, backupPath }); + } catch (err) { + const errorMsg = err instanceof Error ? err.message : String(err); + console.log(` ❌ 修正に失敗しました: ${errorMsg}`); + fixResults.push({ success: false, action, error: errorMsg, backupPath }); + } + } + } + } finally { + rl.close(); + } + + // 結果サマリー + console.log('\n' + '='.repeat(60)); + console.log('📊 修正結果サマリー\n'); + + const successful = fixResults.filter((r) => r.success).length; + const failed = fixResults.filter((r) => !r.success).length; + + console.log(`✅ 成功: ${successful}個`); + console.log(`❌ 失敗: ${failed}個`); + + if (backupFiles.size > 0 && !options.noBackup) { + console.log(`\n💾 バックアップファイル: ${backupFiles.size}個`); + for (const file of backupFiles) { + const backupPath = `${file}.backup`; + console.log(` ${path.relative(PROJECT_ROOT, backupPath)}`); + } + } + + process.exit(failed > 0 ? 1 : 0); +} + +// スクリプトとして実行された場合 +if (require.main === module) { + main().catch((error) => { + console.error('\n❌ エラーが発生しました:', error); + process.exit(1); + }); +} diff --git a/src/core/validate.ts b/src/core/validate.ts new file mode 100644 index 0000000..3e5e7ef --- /dev/null +++ b/src/core/validate.ts @@ -0,0 +1,625 @@ +#!/usr/bin/env tsx + +/** + * ドキュメント内のコード参照(CODE_REF)の整合性をチェックするスクリプト + * + * 使用方法: + * tsx scripts/coderef/validate.ts # 全ファイルを検証 + * tsx scripts/coderef/validate.ts --verbose # 詳細表示 + * tsx scripts/coderef/validate.ts docs/README.md # 特定ファイルのみ検証 + * tsx scripts/coderef/validate.ts docs/backend/ # 特定ディレクトリのみ検証 + * tsx scripts/coderef/validate.ts CLAUDE.md # CLAUDEファイルも可能 + */ + +import * as fs from 'fs'; +import * as path from 'path'; + +import { + findSymbolInAST, + isTypeScriptOrJavaScript, + parseSymbolPath, + selectBestSymbolMatch, +} from '../utils/ast-symbol-search'; +import { + compareCodeContent, + extractLinesFromFile, + searchCodeInFile, +} from '../utils/code-comparison'; +import { displayCodeDiff, displayLineRangeDiff } from '../utils/diff-display'; +import { isIgnored, loadDocsignorePatterns } from '../utils/ignore-pattern'; +import { associateCodeBlocksWithRefs } from '../utils/markdown'; +import type { CodeRef, CodeRefError } from '../utils/types'; + +// 設定 +const DOCS_DIR = path.join(__dirname, '../..', 'docs'); +const PROJECT_ROOT = path.join(__dirname, '../..'); +const DOCSIGNORE_FILE = path.join(PROJECT_ROOT, '.docsignore'); +const CODE_REF_PATTERN = //g; + +// コマンドライン引数のパース +interface CliOptions { + verbose: boolean; + files: string[]; +} + +function parseCliArgs(): CliOptions { + const args = process.argv.slice(2); + const verbose = args.includes('--verbose') || args.includes('-v'); + const files = args.filter((arg) => !arg.startsWith('-')); + + return { verbose, files }; +} + +const { verbose, files: targetFiles } = parseCliArgs(); + +/** + * ディレクトリを再帰的に走査してマークダウンファイルを取得 + */ +export function findMarkdownFiles(dir: string): string[] { + const files: string[] = []; + + function walk(currentPath: string): void { + const entries = fs.readdirSync(currentPath, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(currentPath, entry.name); + + if (entry.isDirectory()) { + walk(fullPath); + } else if (entry.isFile() && entry.name.endsWith('.md')) { + files.push(fullPath); + } + } + } + + walk(dir); + return files; +} + +/** + * コードブロックとインラインコードの範囲を検出 + */ +function getCodeBlockRanges(content: string): Array<{ start: number; end: number }> { + const ranges: Array<{ start: number; end: number }> = []; + + // トリプルバッククォートのコードブロック + const codeBlockPattern = /```[\s\S]*?```/g; + let match: RegExpExecArray | null; + + while ((match = codeBlockPattern.exec(content)) !== null) { + ranges.push({ + start: match.index, + end: match.index + match[0].length, + }); + } + + // インラインコード(バッククォート) + const inlineCodePattern = /`[^`\n]+?`/g; + while ((match = inlineCodePattern.exec(content)) !== null) { + ranges.push({ + start: match.index, + end: match.index + match[0].length, + }); + } + + return ranges; +} + +/** + * 位置がコードブロックまたはインラインコード内かチェック + */ +function isInsideCodeBlock( + position: number, + ranges: Array<{ start: number; end: number }> +): boolean { + return ranges.some((range) => position >= range.start && position < range.end); +} + +/** + * CODE_REFコメントを抽出 + */ +export function extractCodeRefs(content: string, filePath: string): CodeRef[] { + const refs: CodeRef[] = []; + let match: RegExpExecArray | null; + + // コードブロックとインラインコードの範囲を事前に検出 + const codeBlockRanges = getCodeBlockRanges(content); + + while ((match = CODE_REF_PATTERN.exec(content)) !== null) { + const [fullMatch, refPath, symbolPath, startLine, endLine] = match; + + // コードブロックまたはインラインコード内のCODE_REFは除外(サンプル表示用) + if (isInsideCodeBlock(match.index, codeBlockRanges)) { + continue; + } + + // マッチ位置から行番号を計算(1-indexed) + const beforeMatch = content.substring(0, match.index); + const docLineNumber = beforeMatch.split('\n').length; + + // シンボルパスをパース + let className: string | undefined; + let memberName: string | undefined; + if (symbolPath) { + const parsed = parseSymbolPath(symbolPath); + className = parsed.className; + memberName = parsed.memberName; + } + + refs.push({ + fullMatch, + refPath: refPath.trim(), + startLine: startLine ? parseInt(startLine, 10) : null, + endLine: endLine ? parseInt(endLine, 10) : null, + docFile: filePath, + docLineNumber, + codeBlockStartOffset: match.index, // CODE_REFコメントの位置を保存 + symbolPath: symbolPath?.trim(), + className, + memberName, + }); + } + + // コードブロックを関連付け + return associateCodeBlocksWithRefs(content, refs); +} + +/** + * コード内容の検証 + */ +export function validateCodeContent(ref: CodeRef): CodeRefError[] { + const errors: CodeRefError[] = []; + + // 行数指定がない場合の処理 + if (ref.startLine === null || ref.endLine === null) { + // シンボル指定がある場合は、シンボル全体との完全一致検証 + if (ref.symbolPath) { + // コードブロックがない場合はエラーを返す + if (!ref.codeBlock || ref.codeBlock.trim() === '') { + errors.push({ + type: 'CODE_BLOCK_MISSING', + message: `シンボル指定のCODE_REF(${ref.refPath}#${ref.symbolPath})の後にコードブロックが見つかりません。`, + ref, + }); + return errors; + } + + // シンボルの範囲を取得 + const absolutePath = path.resolve(PROJECT_ROOT, ref.refPath); + const fileContent = fs.readFileSync(absolutePath, 'utf-8'); + const matches = findSymbolInAST(fileContent, absolutePath, { + className: ref.className, + memberName: ref.memberName!, + }); + + if (matches.length > 0) { + const symbolMatch = matches[0]; + const symbolCode = extractLinesFromFile( + absolutePath, + symbolMatch.startLine, + symbolMatch.endLine + ); + + // コードブロックとシンボル全体を比較(空白改行は無視) + if (!compareCodeContent(symbolCode, ref.codeBlock)) { + errors.push({ + type: 'CODE_CONTENT_MISMATCH', + message: `シンボル全体とコードブロックが一致しません。`, + ref, + actualCode: symbolCode.substring(0, 200), + expectedCode: ref.codeBlock.substring(0, 200), + }); + } + } + return errors; + } + // シンボル指定がない場合はスキップ(ファイル全体参照) + return errors; + } + + // シンボル指定がある場合でも、行番号指定があれば通常の検証を続行 + + // コードブロックがない場合 + if (!ref.codeBlock || ref.codeBlock.trim() === '') { + const refLocation = + ref.startLine !== null ? `${ref.refPath}:${ref.startLine}-${ref.endLine}` : ref.refPath; + errors.push({ + type: 'CODE_BLOCK_MISSING', + message: `CODE_REF(${refLocation})の後にコードブロックが見つかりません。`, + ref, + }); + return errors; + } + + const absolutePath = path.resolve(PROJECT_ROOT, ref.refPath); + + try { + // 実ファイルから指定行のコードを取得 + const actualCode = extractLinesFromFile(absolutePath, ref.startLine, ref.endLine); + + // コード内容を比較 + if (!compareCodeContent(actualCode, ref.codeBlock)) { + // 一致しない → ファイル全体から検索 + const matches = searchCodeInFile(absolutePath, ref.codeBlock); + + if (matches.length > 0) { + // コードは存在するが別の場所にある + const firstMatch = matches[0]; + const matchInfo = + matches.length > 1 ? `コードは${matches.length}箇所で見つかりました。最初の出現: ` : ''; + + errors.push({ + type: 'CODE_LOCATION_MISMATCH', + message: `${ref.refPath}の行数が一致しません。${matchInfo}(expect: ${ref.startLine}-${ref.endLine}, result: ${firstMatch.start}-${firstMatch.end})`, + ref, + suggestedLines: firstMatch, + }); + } else { + // コード内容が異なる + errors.push({ + type: 'CODE_CONTENT_MISMATCH', + message: `${ref.refPath} のコードが一致しません。`, + ref, + actualCode: actualCode.substring(0, 200), + expectedCode: ref.codeBlock.substring(0, 200), + }); + } + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + errors.push({ + type: 'READ_ERROR', + message: `コード比較中にエラーが発生しました: ${errorMessage}`, + ref, + }); + } + + return errors; +} + +/** + * シンボル指定の検証 + */ +export function validateSymbolRef(ref: CodeRef): CodeRefError[] { + const errors: CodeRefError[] = []; + + // シンボル指定がない場合はスキップ + if (!ref.symbolPath) { + return errors; + } + + const absolutePath = path.resolve(PROJECT_ROOT, ref.refPath); + + // TypeScript/JavaScriptファイルチェック + if (!isTypeScriptOrJavaScript(absolutePath)) { + errors.push({ + type: 'NOT_TYPESCRIPT_FILE', + message: `シンボル指定はTypeScript/JavaScriptファイルのみサポート: ${ref.refPath}`, + ref, + }); + return errors; + } + + const fileContent = fs.readFileSync(absolutePath, 'utf-8'); + + try { + // シンボルを検索 + const matches = findSymbolInAST(fileContent, absolutePath, { + className: ref.className, + memberName: ref.memberName!, + }); + + if (matches.length === 0) { + // シンボルが見つからない + errors.push({ + type: 'SYMBOL_NOT_FOUND', + message: `シンボル "${ref.symbolPath}" が見つかりません`, + ref, + }); + } else if (matches.length > 1 && !ref.startLine) { + // 複数マッチ(行番号ヒントなし) + errors.push({ + type: 'MULTIPLE_SYMBOLS_FOUND', + message: `シンボル "${ref.symbolPath}" が${matches.length}箇所で見つかりました。行番号を指定してください`, + ref, + foundSymbols: matches, + }); + } else { + // 行番号が指定されている場合は検証 + if (ref.startLine && ref.endLine) { + const bestMatch = selectBestSymbolMatch(matches, { + start: ref.startLine, + end: ref.endLine, + }); + + if (bestMatch) { + // 範囲が一致するかチェック + if (bestMatch.startLine !== ref.startLine || bestMatch.endLine !== ref.endLine) { + errors.push({ + type: 'SYMBOL_RANGE_MISMATCH', + message: `シンボル "${ref.symbolPath}" の範囲が一致しません (期待: ${ref.startLine}-${ref.endLine}, 実際: ${bestMatch.startLine}-${bestMatch.endLine})`, + ref, + suggestedSymbol: bestMatch, + }); + } + } + } + } + } catch (error) { + errors.push({ + type: 'READ_ERROR', + message: `AST解析エラー: ${error instanceof Error ? error.message : String(error)}`, + ref, + }); + } + + return errors; +} + +/** + * 参照先のファイルと行番号の存在を確認 + */ +export function validateCodeRef(ref: CodeRef): CodeRefError[] { + const errors: CodeRefError[] = []; + + // 相対パスを絶対パスに変換(プロジェクトルートからの相対パス) + const absolutePath = path.resolve(PROJECT_ROOT, ref.refPath); + + // パストラバーサル攻撃を防ぐ: プロジェクトルート内に留まるか検証 + if (!absolutePath.startsWith(PROJECT_ROOT + path.sep)) { + errors.push({ + type: 'PATH_TRAVERSAL', + message: `参照先のパスがプロジェクトルート外を指しています: ${ref.refPath}`, + ref, + }); + return errors; + } + + // ファイルの存在確認 + if (!fs.existsSync(absolutePath)) { + errors.push({ + type: 'FILE_NOT_FOUND', + message: `参照先のファイルが見つかりません: ${ref.refPath}`, + ref, + }); + return errors; + } + + // 行番号が指定されている場合、行数をチェック + if (ref.startLine !== null && ref.endLine !== null) { + try { + const content = fs.readFileSync(absolutePath, 'utf-8'); + const lines = content.split('\n'); + const totalLines = lines.length; + + if (ref.startLine < 1) { + errors.push({ + type: 'INVALID_LINE_NUMBER', + message: `開始行番号が無効です(1未満): ${ref.startLine}`, + ref, + }); + } + + if (ref.endLine > totalLines) { + errors.push({ + type: 'LINE_OUT_OF_RANGE', + message: `終了行番号がファイルの行数を超えています: ${ref.endLine} > ${totalLines}`, + ref, + }); + } + + if (ref.startLine > ref.endLine) { + errors.push({ + type: 'INVALID_RANGE', + message: `開始行番号が終了行番号より大きいです: ${ref.startLine} > ${ref.endLine}`, + ref, + }); + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + errors.push({ + type: 'READ_ERROR', + message: `ファイルの読み込みに失敗しました: ${errorMessage}`, + ref, + }); + } + } + + // シンボル指定がある場合はシンボルバリデーション + if (errors.length === 0 && ref.symbolPath) { + const symbolErrors = validateSymbolRef(ref); + errors.push(...symbolErrors); + } + + // コード内容の検証(既存のエラーがない場合のみ) + if (errors.length === 0) { + const contentErrors = validateCodeContent(ref); + errors.push(...contentErrors); + } + + return errors; +} + +/** + * 指定されたパスがディレクトリかどうかを判定 + */ +function isDirectory(filePath: string): boolean { + try { + return fs.statSync(filePath).isDirectory(); + } catch { + return false; + } +} + +/** + * 対象ファイルを解決する + * - ファイル指定がない場合: 全ファイル + * - ファイル指定がある場合: 指定されたファイル/ディレクトリのみ + */ +function resolveTargetFiles(targets: string[]): string[] { + if (targets.length === 0) { + // ファイル指定がない場合は全ファイルを対象 + return findMarkdownFiles(DOCS_DIR); + } + + const resolvedFiles = new Set(); + + for (const target of targets) { + // 相対パスを絶対パスに変換 + const absolutePath = path.isAbsolute(target) ? target : path.join(PROJECT_ROOT, target); + + if (isDirectory(absolutePath)) { + // ディレクトリの場合は再帰的にマークダウンファイルを検索 + const files = findMarkdownFiles(absolutePath); + files.forEach((file) => resolvedFiles.add(file)); + } else if (fs.existsSync(absolutePath)) { + // ファイルの場合はそのまま追加 + if (absolutePath.endsWith('.md')) { + resolvedFiles.add(absolutePath); + } + } else { + console.warn(`⚠️ ファイルが見つかりません: ${target}`); + } + } + + return Array.from(resolvedFiles); +} + +export async function main(): Promise { + console.log('🔍 ドキュメント内のコード参照を検証しています...\n'); + + // 対象ファイルを解決 + const allMarkdownFiles = resolveTargetFiles(targetFiles); + + if (targetFiles.length > 0 && verbose) { + console.log(`📋 指定されたファイル/ディレクトリ: ${targetFiles.join(', ')}\n`); + } + + // .docsignoreパターンを読み込み + const ignorePatterns = loadDocsignorePatterns(DOCSIGNORE_FILE); + if (verbose) { + console.log(`📋 .docsignoreから${ignorePatterns.length}個のパターンを読み込みました\n`); + } + + // .docsignoreで除外されていないファイルのみを対象とする + const markdownFiles = allMarkdownFiles.filter((file) => { + const relativePath = path.relative(PROJECT_ROOT, file); + return !isIgnored(relativePath, ignorePatterns); + }); + + if (verbose && allMarkdownFiles.length > markdownFiles.length) { + console.log( + `📋 ${allMarkdownFiles.length - markdownFiles.length}個のファイルが.docsignoreにより除外されました\n` + ); + } + + console.log(`📄 ${markdownFiles.length} 個のマークダウンファイルを検出\n`); + + // 全てのCODE_REFを抽出 + let totalRefs = 0; + const allRefs: CodeRef[] = []; + + for (const file of markdownFiles) { + const content = fs.readFileSync(file, 'utf-8'); + const refs = extractCodeRefs(content, file); + + if (refs.length > 0) { + totalRefs += refs.length; + allRefs.push(...refs); + + if (verbose) { + console.log(` ${path.relative(DOCS_DIR, file)}: ${refs.length} 個の参照`); + } + } + } + + console.log(`\n📌 ${totalRefs} 個のコード参照を検出\n`); + + if (totalRefs === 0) { + console.log('✅ コード参照が見つかりませんでした(検証不要)'); + process.exit(0); + } + + // 各参照を検証 + const allErrors = await Promise.all(allRefs.map((ref) => validateCodeRef(ref))).then((results) => + results.flat() + ); + + // 結果の表示 + if (allErrors.length === 0) { + console.log('✅ 全てのコード参照が有効です!'); + process.exit(0); + } else { + console.log(`❌ ${allErrors.length} 個のエラーが見つかりました:\n`); + + // エラーをグループ化して表示 + const errorsByDoc: Record = {}; + + for (const error of allErrors) { + const docFile = path.relative(PROJECT_ROOT, error.ref.docFile); + + if (!errorsByDoc[docFile]) { + errorsByDoc[docFile] = []; + } + + errorsByDoc[docFile].push(error); + } + + // エラー詳細の表示 + for (const [docFile, errors] of Object.entries(errorsByDoc)) { + console.log(`📄 ${docFile}:`); + + for (const error of errors) { + console.log(` ❌ ${error.type}: ${error.message}`); + + // ドキュメント内の行番号を表示 + const filePath = path.relative(PROJECT_ROOT, error.ref.docFile); + const lineInfo = error.ref.docLineNumber ? `:${error.ref.docLineNumber}` : ''; + console.log(` ${filePath}${lineInfo}: ${error.ref.fullMatch}`); + + // CODE_LOCATION_MISMATCHの場合、行範囲の差分を表示 + if (error.type === 'CODE_LOCATION_MISMATCH' && error.suggestedLines && verbose) { + // verboseモードでは詳細な差分を表示 + const filePath = path.join(PROJECT_ROOT, error.ref.refPath); + const actualCode = extractLinesFromFile( + filePath, + error.suggestedLines.start, + error.suggestedLines.end + ); + const diff = displayLineRangeDiff( + actualCode, + { start: error.ref.startLine!, end: error.ref.endLine! }, + error.suggestedLines + ); + console.log(diff); + } + + // CODE_CONTENT_MISMATCHの場合、差分を表示 + if ( + error.type === 'CODE_CONTENT_MISMATCH' && + verbose && + error.expectedCode && + error.actualCode + ) { + // verboseモードでは詳細な差分を表示 + const diff = displayCodeDiff(error.expectedCode, error.actualCode); + console.log(diff); + } + + console.log(''); + } + } + + console.log(`\n💡 ヒント:`); + console.log(` - ファイルパスがプロジェクトルートからの相対パスになっているか確認してください`); + console.log(` - 行番号が最新のコードと一致しているか確認してください`); + console.log(` - 詳細情報を表示するには --verbose オプションを使用してください`); + + process.exit(1); + } +} + +// スクリプトが直接実行された場合のみmainを実行 +if (require.main === module) { + main(); +} diff --git a/src/utils/ast-scope-expansion.ts b/src/utils/ast-scope-expansion.ts new file mode 100644 index 0000000..c95c55d --- /dev/null +++ b/src/utils/ast-scope-expansion.ts @@ -0,0 +1,306 @@ +/** + * AST解析によるスコープ拡張ユーティリティ + */ + +import * as path from 'path'; + +import { parse as parseTypeScript } from '@typescript-eslint/typescript-estree'; +import type { TSESTree } from '@typescript-eslint/typescript-estree'; + +import type { ExpandedMatch } from './types'; + +/** + * マッチ拡張のオプション + */ +export interface MatchExpansionOptions { + filePath: string; + originalMatch: { start: number; end: number }; + fileContent: string; +} + +/** + * AST拡張の結果 + */ +interface ASTExpansionResult { + success: boolean; + expandedMatches?: ExpandedMatch[]; + error?: string; +} + +/** + * ファイルがTypeScript/JavaScriptかどうかを判定 + */ +function isTypeScriptOrJavaScript(filePath: string): boolean { + const ext = path.extname(filePath).toLowerCase(); + return ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'].includes(ext); +} + +/** + * マッチ位置を含むASTノードを見つける + */ +function findNodeAtLine(ast: TSESTree.Program, targetLine: number): TSESTree.Node | null { + let foundNode: TSESTree.Node | null = null; + + function visit(node: TSESTree.Node) { + if (!node.loc) return; + + const nodeStart = node.loc.start.line; + const nodeEnd = node.loc.end.line; + + // ターゲット行がノードの範囲内にある場合 + if (nodeStart <= targetLine && targetLine <= nodeEnd) { + foundNode = node; + + // 子ノードを探索(より具体的なノードを見つけるため) + const keys = Object.keys(node) as Array; + for (const key of keys) { + const value = node[key]; + if (value && typeof value === 'object') { + if (Array.isArray(value)) { + value.forEach((child) => { + if (child && typeof child === 'object' && 'type' in child) { + visit(child as TSESTree.Node); + } + }); + } else if ('type' in value) { + visit(value as TSESTree.Node); + } + } + } + } + } + + visit(ast); + return foundNode; +} + +/** + * ノードのスコープタイプを判定 + */ +function getScopeType(node: TSESTree.Node): ExpandedMatch['scopeType'] { + switch (node.type) { + case 'FunctionDeclaration': + case 'FunctionExpression': + case 'ArrowFunctionExpression': + case 'MethodDefinition': + return 'function'; + case 'ClassDeclaration': + case 'ClassExpression': + return 'class'; + case 'TSInterfaceDeclaration': + return 'interface'; + case 'TSTypeAliasDeclaration': + return 'type'; + case 'VariableDeclaration': + return 'const'; + default: + return 'unknown'; + } +} + +/** + * ノードの親スコープを探索 + */ +function findParentScope(ast: TSESTree.Program, targetNode: TSESTree.Node): TSESTree.Node | null { + let parentScope: TSESTree.Node | null = null; + let currentDepth = 0; + let targetDepth = -1; + + function visit(node: TSESTree.Node, depth: number) { + const isScope = + node.type === 'FunctionDeclaration' || + node.type === 'FunctionExpression' || + node.type === 'ArrowFunctionExpression' || + node.type === 'MethodDefinition' || + node.type === 'ClassDeclaration' || + node.type === 'ClassExpression' || + node.type === 'TSInterfaceDeclaration' || + node.type === 'TSTypeAliasDeclaration' || + (node.type === 'VariableDeclaration' && node.parent?.type === 'ExportNamedDeclaration'); + + if (node === targetNode) { + targetDepth = depth; + return; + } + + if (targetDepth === -1 && node.loc && targetNode.loc) { + const nodeStart = node.loc.start.line; + const nodeEnd = node.loc.end.line; + const targetStart = targetNode.loc.start.line; + const targetEnd = targetNode.loc.end.line; + + // ターゲットノードがこのノードの範囲内にある場合 + if (nodeStart <= targetStart && targetEnd <= nodeEnd && isScope && depth > currentDepth) { + parentScope = node; + currentDepth = depth; + } + } + + // 子ノードを探索 + const keys = Object.keys(node) as Array; + for (const key of keys) { + const value = node[key]; + if (value && typeof value === 'object') { + if (Array.isArray(value)) { + value.forEach((child) => { + if (child && typeof child === 'object' && 'type' in child) { + visit(child as TSESTree.Node, depth + 1); + } + }); + } else if ('type' in value) { + visit(value as TSESTree.Node, depth + 1); + } + } + } + } + + visit(ast, 0); + return parentScope; +} + +/** + * JSDocコメントを含めた開始位置を取得 + */ +function getStartLineWithJSDoc(node: TSESTree.Node, fileContent: string): number { + if (!node.loc) return 1; // locがない場合は1行目を返す + + const nodeStartLine = node.loc.start.line; + const lines = fileContent.split('\n'); + + // ノードの直前の行から逆順に探索 + for (let i = nodeStartLine - 2; i >= 0; i--) { + const line = lines[i].trim(); + + // JSDocコメントの終了を見つけた + if (line.endsWith('*/')) { + // JSDocコメントの開始を探す + for (let j = i; j >= 0; j--) { + if (lines[j].trim().startsWith('/**')) { + return j + 1; // 1-indexed + } + } + } + + // 空行またはコメント以外があったら終了 + if (line && !line.startsWith('//') && !line.startsWith('*')) { + break; + } + } + + return nodeStartLine; +} + +/** + * AST解析によるスコープ拡張を試みる + */ +function tryASTExpansion( + filePath: string, + originalMatch: { start: number; end: number }, + fileContent: string +): ASTExpansionResult { + try { + // TypeScript/JavaScriptファイルのみ処理 + if (!isTypeScriptOrJavaScript(filePath)) { + return { success: false, error: 'Not a TypeScript/JavaScript file' }; + } + + // ASTをパース + const ast = parseTypeScript(fileContent, { + loc: true, + range: true, + comment: true, + tokens: true, + }); + + // マッチ位置のノードを見つける + const matchStartNode = findNodeAtLine(ast, originalMatch.start); + const matchEndNode = findNodeAtLine(ast, originalMatch.end); + + if (!matchStartNode || !matchEndNode) { + return { success: false, error: 'Node not found at match position' }; + } + + // より具体的なノード(小さい方)を選択 + const targetNode = + matchStartNode.loc && matchEndNode.loc + ? matchStartNode.loc.end.line - matchStartNode.loc.start.line < + matchEndNode.loc.end.line - matchEndNode.loc.start.line + ? matchStartNode + : matchEndNode + : matchStartNode; + + // 親スコープを探索 + const parentScope = findParentScope(ast, targetNode); + + if (!parentScope || !parentScope.loc) { + // 親スコープが見つからない場合、元のマッチを返す + return { + success: true, + expandedMatches: [ + { + start: originalMatch.start, + end: originalMatch.end, + confidence: 'low', + expansionType: 'none', + scopeType: 'unknown', + }, + ], + }; + } + + // JSDocを含めた開始位置を取得 + const startLine = getStartLineWithJSDoc(parentScope, fileContent); + const endLine = parentScope.loc.end.line; + const scopeType = getScopeType(parentScope); + + return { + success: true, + expandedMatches: [ + { + start: startLine, + end: endLine, + confidence: 'high', + expansionType: 'ast', + scopeType, + }, + ], + }; + } catch (error) { + if (error instanceof Error) { + if (error.name === 'SyntaxError' || error.message.includes('Unexpected token')) { + return { success: false, error: `構文エラー: ${error.message}` }; + } + return { success: false, error: error.message }; + } + return { success: false, error: String(error) }; + } +} + +/** + * マッチ位置をスコープ拡張する + * + * @param options 拡張オプション + * @returns 拡張されたマッチ + */ +export function expandMatchToScope(options: MatchExpansionOptions): ExpandedMatch[] { + const { filePath, originalMatch, fileContent } = options; + + // AST解析を試みる + const astResult = tryASTExpansion(filePath, originalMatch, fileContent); + + if (astResult.success && astResult.expandedMatches) { + return astResult.expandedMatches; + } + + // AST解析が失敗した場合、元のマッチを返す(フォールバック) + console.warn(`⚠️ AST解析失敗: ${astResult.error}`); + return [ + { + start: originalMatch.start, + end: originalMatch.end, + confidence: 'low', + expansionType: 'none', + scopeType: 'unknown', + }, + ]; +} diff --git a/src/utils/ast-symbol-search.ts b/src/utils/ast-symbol-search.ts new file mode 100644 index 0000000..da2bc93 --- /dev/null +++ b/src/utils/ast-symbol-search.ts @@ -0,0 +1,265 @@ +/** + * AST解析によるシンボル検索ユーティリティ + */ + +import * as path from 'path'; + +import { parse as parseTypeScript } from '@typescript-eslint/typescript-estree'; +import type { TSESTree } from '@typescript-eslint/typescript-estree'; + +import type { SymbolMatch } from './types'; + +/** + * ASTキャッシュ(同じファイルを複数回パースしない) + */ +const astCache = new Map(); + +/** + * ファイルがTypeScript/JavaScriptかどうかを判定 + */ +export function isTypeScriptOrJavaScript(filePath: string): boolean { + const ext = path.extname(filePath).toLowerCase(); + return ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'].includes(ext); +} + +/** + * ASTをパースまたはキャッシュから取得 + */ +function getOrParseAST(filePath: string, fileContent: string): TSESTree.Program { + if (astCache.has(filePath)) { + return astCache.get(filePath)!; + } + + const ast = parseTypeScript(fileContent, { + loc: true, + range: true, + comment: true, + jsx: filePath.endsWith('.tsx') || filePath.endsWith('.jsx'), + }); + + astCache.set(filePath, ast); + return ast; +} + +/** + * JSDocコメントを含む開始行を取得 + */ +function getStartLineWithJSDoc(node: TSESTree.Node, fileContent: string): number { + if (!node.loc) return 1; + + const lines = fileContent.split('\n'); + let startLine = node.loc.start.line; + + // ノードの直前の行から上に向かってJSDocを探す + for (let i = startLine - 2; i >= 0; i--) { + const line = lines[i].trim(); + + // JSDocの終わり(*/)を見つけた場合 + if (line.endsWith('*/')) { + // さらに上に向かってJSDocの始まり(/**)を探す + for (let j = i; j >= 0; j--) { + const docLine = lines[j].trim(); + if (docLine.startsWith('/**')) { + return j + 1; // 1-indexed + } + } + break; + } + + // 空行やコメントでない行が見つかったら終了 + if (line.length > 0 && !line.startsWith('//')) { + break; + } + } + + return startLine; +} + +/** + * クラス名でクラスを検索 + */ +function findClassByName(ast: TSESTree.Program, className: string): TSESTree.ClassDeclaration[] { + const classes: TSESTree.ClassDeclaration[] = []; + + function visit(node: TSESTree.Node) { + if (node.type === 'ClassDeclaration' && node.id && node.id.name === className) { + classes.push(node); + } + + // 子ノードを再帰的に探索 + const keys = Object.keys(node) as Array; + for (const key of keys) { + const value = node[key]; + if (value && typeof value === 'object') { + if (Array.isArray(value)) { + value.forEach((child) => { + if (child && typeof child === 'object' && 'type' in child) { + visit(child as TSESTree.Node); + } + }); + } else if ('type' in value) { + visit(value as TSESTree.Node); + } + } + } + } + + visit(ast); + return classes; +} + +/** + * クラス内のメソッドを検索 + */ +function findMethodInClass( + classNode: TSESTree.ClassDeclaration, + methodName: string +): TSESTree.MethodDefinition | null { + if (!classNode.body || !classNode.body.body) return null; + + for (const member of classNode.body.body) { + if (member.type === 'MethodDefinition' && member.key.type === 'Identifier') { + if (member.key.name === methodName) { + return member; + } + } + } + + return null; +} + +/** + * トップレベル関数を検索 + */ +function findFunctionByName( + ast: TSESTree.Program, + functionName: string +): TSESTree.FunctionDeclaration[] { + const functions: TSESTree.FunctionDeclaration[] = []; + + // トップレベルの関数のみ検索 + for (const statement of ast.body) { + if ( + statement.type === 'FunctionDeclaration' && + statement.id && + statement.id.name === functionName + ) { + functions.push(statement); + } else if (statement.type === 'ExportNamedDeclaration' && statement.declaration) { + const decl = statement.declaration; + if (decl.type === 'FunctionDeclaration' && decl.id && decl.id.name === functionName) { + functions.push(decl); + } + } + } + + return functions; +} + +/** + * シンボルパスをパースしてクラス名とメンバー名に分割 + */ +export function parseSymbolPath(symbolPath: string): { + className?: string; + memberName: string; +} { + const parts = symbolPath.split('#'); + if (parts.length === 1) { + // 関数のみ: "functionName" + return { memberName: parts[0].trim() }; + } else if (parts.length === 2) { + // クラス+メソッド: "ClassName#methodName" + return { + className: parts[0].trim(), + memberName: parts[1].trim(), + }; + } + throw new Error(`Invalid symbol path: ${symbolPath}`); +} + +/** + * ASTを使ってシンボルを検索 + */ +export function findSymbolInAST( + fileContent: string, + filePath: string, + options: { + className?: string; + memberName: string; + } +): SymbolMatch[] { + // TypeScript/JavaScriptファイルのみ + if (!isTypeScriptOrJavaScript(filePath)) { + throw new Error('TypeScript/JavaScript files only'); + } + + const ast = getOrParseAST(filePath, fileContent); + const matches: SymbolMatch[] = []; + + if (options.className) { + // クラス+メソッドを検索 + const classes = findClassByName(ast, options.className); + + for (const classNode of classes) { + const method = findMethodInClass(classNode, options.memberName); + if (method && method.loc) { + matches.push({ + className: options.className, + memberName: options.memberName, + startLine: getStartLineWithJSDoc(method, fileContent), + endLine: method.loc.end.line, + scopeType: 'method', + confidence: 'high', + }); + } + } + } else { + // 関数を検索(トップレベル) + const functions = findFunctionByName(ast, options.memberName); + + for (const funcNode of functions) { + if (funcNode.loc) { + matches.push({ + memberName: options.memberName, + startLine: getStartLineWithJSDoc(funcNode, fileContent), + endLine: funcNode.loc.end.line, + scopeType: 'function', + confidence: 'high', + }); + } + } + } + + return matches; +} + +/** + * 複数マッチ時の最適な選択 + */ +export function selectBestSymbolMatch( + matches: SymbolMatch[], + hintLines?: { start: number; end: number } +): SymbolMatch | null { + if (matches.length === 0) return null; + if (matches.length === 1) return matches[0]; + + // 行番号ヒントがある場合、最も近いものを選択 + if (hintLines) { + return matches.reduce((best, current) => { + const bestDistance = Math.abs(best.startLine - hintLines.start); + const currentDistance = Math.abs(current.startLine - hintLines.start); + return currentDistance < bestDistance ? current : best; + }); + } + + // 信頼度が最も高いものを選択 + const confidenceScore = { high: 3, medium: 2, low: 1 }; + return matches.sort((a, b) => confidenceScore[b.confidence] - confidenceScore[a.confidence])[0]; +} + +/** + * ASTキャッシュをクリア + */ +export function clearASTCache(): void { + astCache.clear(); +} diff --git a/src/utils/backup.ts b/src/utils/backup.ts new file mode 100644 index 0000000..bc8647a --- /dev/null +++ b/src/utils/backup.ts @@ -0,0 +1,57 @@ +/** + * バックアップ管理ユーティリティ + */ + +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * ファイルのバックアップを作成 + * @returns バックアップファイルのパス + */ +export function createBackup(filePath: string): string { + let backupPath = `${filePath}.backup`; + let counter = 1; + + // ユニークなバックアップファイル名を検索 + while (fs.existsSync(backupPath)) { + backupPath = `${filePath}.backup.${counter}`; + counter++; + } + + fs.copyFileSync(filePath, backupPath); + return backupPath; +} + +/** + * バックアップから復元 + */ +export function restoreBackup(backupPath: string, originalPath: string): void { + if (!fs.existsSync(backupPath)) { + throw new Error(`バックアップファイルが見つかりません: ${backupPath}`); + } + + fs.copyFileSync(backupPath, originalPath); +} + +/** + * バックアップファイルを削除 + */ +export function deleteBackup(backupPath: string): void { + if (fs.existsSync(backupPath)) { + fs.unlinkSync(backupPath); + } +} + +/** + * 指定ファイルの全バックアップファイルをリスト + */ +export function listBackups(filePath: string): string[] { + const dir = path.dirname(filePath); + const filename = path.basename(filePath); + + const files = fs.readdirSync(dir); + const backupPattern = new RegExp(`^${filename}\\.backup(\\.\\d+)?$`); + + return files.filter((f) => backupPattern.test(f)).map((f) => path.join(dir, f)); +} diff --git a/src/utils/code-comparison.ts b/src/utils/code-comparison.ts new file mode 100644 index 0000000..9dd6803 --- /dev/null +++ b/src/utils/code-comparison.ts @@ -0,0 +1,141 @@ +/** + * コード比較ユーティリティ + */ + +import * as fs from 'fs'; + +import { expandMatchToScope } from './ast-scope-expansion'; +import { normalizeCode } from './markdown'; +import type { ExpandedMatch } from './types'; + +/** + * コードから共通の先頭インデントを除去する + * + * @param code コード文字列 + * @returns インデントを除去したコード + */ +export function dedentCode(code: string): string { + const lines = code.split('\n'); + + // 空行を除いた最小インデントを見つける + let minIndent = Infinity; + for (const line of lines) { + if (line.trim().length === 0) continue; // 空行はスキップ + + const indent = line.match(/^(\s*)/)?.[1].length ?? 0; + minIndent = Math.min(minIndent, indent); + } + + // 全行から最小インデントを除去 + if (minIndent === Infinity || minIndent === 0) { + return code; + } + + return lines.map((line) => (line.trim().length === 0 ? line : line.slice(minIndent))).join('\n'); +} + +/** + * ファイルから指定行範囲のコードを抽出する + * + * @param filePath ファイルパス + * @param startLine 開始行(1-indexed) + * @param endLine 終了行(1-indexed) + * @returns 抽出されたコード(先頭インデントを除去) + */ +export function extractLinesFromFile(filePath: string, startLine: number, endLine: number): string { + const content = fs.readFileSync(filePath, 'utf-8'); + const lines = content.split('\n'); + + // 1-indexedから0-indexedに変換し、指定範囲を抽出 + const extractedLines = lines.slice(startLine - 1, endLine); + const extractedCode = extractedLines.join('\n'); + + // 共通の先頭インデントを除去 + return dedentCode(extractedCode); +} + +/** + * 2つのコード文字列を比較する(正規化して比較) + * + * @param actual 実際のコード + * @param expected 期待されるコード + * @returns 一致する場合true + */ +export function compareCodeContent(actual: string, expected: string): boolean { + const normalizedActual = normalizeCode(actual); + const normalizedExpected = normalizeCode(expected); + + return normalizedActual === normalizedExpected; +} + +/** + * ファイル全体から指定されたコードを検索する(スライディングウィンドウ) + * + * @param filePath ファイルパス + * @param codeToFind 検索するコード + * @returns 見つかった位置の配列(start/end行番号、1-indexed) + */ +export function searchCodeInFile( + filePath: string, + codeToFind: string +): { start: number; end: number }[] { + const fileContent = fs.readFileSync(filePath, 'utf-8'); + const fileLines = fileContent.split('\n'); + + // ターゲットコードの行数を取得(正規化前の元のコードから) + const targetLineCount = codeToFind.split('\n').length; + + // 正規化後のターゲットコード + const normalizedTarget = normalizeCode(codeToFind); + + const matches: { start: number; end: number }[] = []; + + // ファイル全体をスライディングウィンドウで走査 + for (let i = 0; i <= fileLines.length - targetLineCount; i++) { + // 現在のウィンドウを抽出 + const windowLines = fileLines.slice(i, i + targetLineCount); + const windowCode = windowLines.join('\n'); + const normalizedWindow = normalizeCode(windowCode); + + // 正規化して比較 + if (normalizedWindow === normalizedTarget) { + matches.push({ + start: i + 1, // 1-indexedに変換 + end: i + targetLineCount, // 1-indexed + }); + } + } + + return matches; +} + +/** + * ファイル全体から指定されたコードを検索し、スコープ拡張を適用する + * + * @param filePath ファイルパス + * @param codeToFind 検索するコード + * @returns 見つかった位置の配列(スコープ拡張済み、信頼度情報付き) + */ +export function searchCodeInFileWithScopeExpansion( + filePath: string, + codeToFind: string +): ExpandedMatch[] { + const fileContent = fs.readFileSync(filePath, 'utf-8'); + + // 既存のsearchCodeInFile()を使用してマッチを取得 + const rawMatches = searchCodeInFile(filePath, codeToFind); + + // 各マッチに対してスコープ拡張を実行 + const expandedMatches = rawMatches.map((match) => { + const expanded = expandMatchToScope({ + filePath, + originalMatch: match, + fileContent, + }); + + // expandMatchToScopeは配列を返すが、ここでは最初の要素のみを使用 + return expanded[0] || { ...match, confidence: 'low' as const, expansionType: 'none' as const }; + }); + + return expandedMatches; +} diff --git a/src/utils/code-ellipsis.ts b/src/utils/code-ellipsis.ts new file mode 100644 index 0000000..53262d8 --- /dev/null +++ b/src/utils/code-ellipsis.ts @@ -0,0 +1,230 @@ +/** + * コード省略表示ユーティリティ + */ + +import * as fs from 'fs'; + +import { parse as parseTypeScript } from '@typescript-eslint/typescript-estree'; +import type { TSESTree } from '@typescript-eslint/typescript-estree'; + +import { isTypeScriptOrJavaScript } from './ast-symbol-search'; + +const ELLIPSIS = ' // ... (省略) ...'; + +/** + * クラスメンバー情報 + */ +interface ClassMember { + name: string; + startLine: number; + endLine: number; + type: 'method' | 'property' | 'constructor'; +} + +/** + * ASTからクラスノードを検索 + */ +function findClassNode(fileContent: string, className: string): TSESTree.ClassDeclaration | null { + const ast = parseTypeScript(fileContent, { + loc: true, + range: true, + jsx: fileContent.includes('tsx') || fileContent.includes('jsx'), + }); + + function visit(node: TSESTree.Node): TSESTree.ClassDeclaration | null { + if (node.type === 'ClassDeclaration' && node.id && node.id.name === className) { + return node; + } + + // 子ノードを再帰的に探索 + const keys = Object.keys(node) as Array; + for (const key of keys) { + const value = node[key]; + if (value && typeof value === 'object') { + if (Array.isArray(value)) { + for (const child of value) { + if (child && typeof child === 'object' && 'type' in child) { + const found = visit(child as TSESTree.Node); + if (found) return found; + } + } + } else if ('type' in value) { + const found = visit(value as TSESTree.Node); + if (found) return found; + } + } + } + + return null; + } + + return visit(ast); +} + +/** + * クラス内のメンバーを取得 + */ +function getClassMembers(classNode: TSESTree.ClassDeclaration): ClassMember[] { + const members: ClassMember[] = []; + + if (!classNode.body || !classNode.body.body) { + return members; + } + + for (const member of classNode.body.body) { + if (!member.loc) continue; + + if (member.type === 'MethodDefinition') { + const name = + member.key.type === 'Identifier' + ? member.key.name + : member.kind === 'constructor' + ? 'constructor' + : 'unknown'; + + members.push({ + name, + startLine: member.loc.start.line, + endLine: member.loc.end.line, + type: member.kind === 'constructor' ? 'constructor' : 'method', + }); + } else if (member.type === 'PropertyDefinition') { + const name = member.key.type === 'Identifier' ? member.key.name : 'unknown'; + + members.push({ + name, + startLine: member.loc.start.line, + endLine: member.loc.end.line, + type: 'property', + }); + } + } + + return members; +} + +/** + * JSDocコメントを含む開始行を取得 + */ +function getStartLineWithJSDoc(startLine: number, lines: string[]): number { + // 開始行の直前から上に向かってJSDocを探す + for (let i = startLine - 2; i >= 0; i--) { + const line = lines[i].trim(); + + // JSDocの終わり(*/)を見つけた場合 + if (line.endsWith('*/')) { + // さらに上に向かってJSDocの始まり(/**)を探す + for (let j = i; j >= 0; j--) { + const docLine = lines[j].trim(); + if (docLine.startsWith('/**')) { + return j + 1; // 1-indexed + } + } + break; + } + + // 空行やコメントでない行が見つかったら終了 + if (line.length > 0 && !line.startsWith('//')) { + break; + } + } + + return startLine; +} + +/** + * コードに省略記号を挿入 + */ +export function insertEllipsis( + filePath: string, + options: { + className?: string; + memberName: string; + } +): string { + // TypeScript/JavaScriptファイルのみ + if (!isTypeScriptOrJavaScript(filePath)) { + throw new Error('TypeScript/JavaScript files only'); + } + + const fileContent = fs.readFileSync(filePath, 'utf-8'); + const lines = fileContent.split('\n'); + + if (options.className) { + // クラス内のメソッドのみ表示 + const classNode = findClassNode(fileContent, options.className); + if (!classNode || !classNode.loc) { + // クラスが見つからない場合は元のファイル内容を返す + return fileContent; + } + + const result: string[] = []; + const members = getClassMembers(classNode); + + // クラス宣言行を取得(export class ClassName { の行) + const classStartLine = classNode.loc.start.line; + const classEndLine = classNode.loc.end.line; + + // クラス宣言の開始行を追加 + result.push(lines[classStartLine - 1]); + + let lastEndLine = classStartLine; + let hasAddedEllipsis = false; + + for (const member of members) { + if (member.name === options.memberName) { + // ターゲットメソッド: JSDocコメントを含めて完全に表示 + const memberStartWithJSDoc = getStartLineWithJSDoc(member.startLine, lines); + + // 前のメンバーとの間に省略記号を挿入(まだ追加していない場合) + if (lastEndLine < memberStartWithJSDoc && !hasAddedEllipsis) { + result.push(''); + result.push(ELLIPSIS); + result.push(''); + hasAddedEllipsis = true; + } + + // ターゲットメソッドを追加 + for (let i = memberStartWithJSDoc - 1; i < member.endLine; i++) { + result.push(lines[i]); + } + + lastEndLine = member.endLine; + hasAddedEllipsis = false; + } else { + // 他のメンバー: 省略(省略記号は一度だけ追加) + if (!hasAddedEllipsis && lastEndLine < member.endLine) { + hasAddedEllipsis = true; + } + lastEndLine = member.endLine; + } + } + + // 最後のメンバーの後に省略記号を追加(必要な場合) + if (hasAddedEllipsis && lastEndLine < classEndLine - 1) { + result.push(''); + result.push(ELLIPSIS); + result.push(''); + } + + // クラスの閉じ括弧を追加 + result.push(lines[classEndLine - 1]); + + return result.join('\n'); + } else { + // 関数のみ表示(前後のコンテキストは省略) + // この場合は、シンボル検索で見つかった範囲のみを返す + // 実装が必要な場合は追加 + return fileContent; + } +} + +/** + * 省略表示されたコードから省略記号を除去 + */ +export function removeEllipsis(code: string): string { + return code + .split('\n') + .filter((line) => !line.trim().includes('// ... (省略) ...')) + .join('\n'); +} diff --git a/src/utils/diff-display.ts b/src/utils/diff-display.ts new file mode 100644 index 0000000..fc4ba6c --- /dev/null +++ b/src/utils/diff-display.ts @@ -0,0 +1,125 @@ +/** + * コードの差分を視覚的に表示するユーティリティ + */ + +// ANSIカラーコード +const COLORS = { + RED: '\x1b[31m', + GREEN: '\x1b[32m', + RESET: '\x1b[0m', + DIM: '\x1b[2m', +}; + +/** + * 2つのコードブロックの差分を行単位で表示 + * @param expected 期待されるコード(ドキュメント内のコードブロック) + * @param actual 実際のコード(ファイルから取得したコード) + * @returns 色付けされた差分表示の文字列 + */ +export function displayCodeDiff(expected: string, actual: string): string { + const expectedLines = expected.split('\n'); + const actualLines = actual.split('\n'); + + const output: string[] = []; + const maxLines = Math.max(expectedLines.length, actualLines.length); + + // ヘッダー + output.push( + `${COLORS.DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.RESET}` + ); + output.push(`${COLORS.RED}- 期待されるコード (ドキュメント内)${COLORS.RESET}`); + output.push(`${COLORS.GREEN}+ 実際のコード (ファイル内)${COLORS.RESET}`); + output.push( + `${COLORS.DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.RESET}` + ); + + // 行ごとに比較 + for (let i = 0; i < maxLines; i++) { + const expectedLine = expectedLines[i]; + const actualLine = actualLines[i]; + + if (expectedLine === actualLine) { + // 一致する行(両方存在する場合) + if (expectedLine !== undefined) { + output.push(` ${expectedLine}`); + } + } else { + // 期待される行(削除された行) + if (expectedLine !== undefined) { + output.push(`${COLORS.RED}- ${expectedLine}${COLORS.RESET}`); + } + // 実際の行(追加された行) + if (actualLine !== undefined) { + output.push(`${COLORS.GREEN}+ ${actualLine}${COLORS.RESET}`); + } + } + } + + output.push( + `${COLORS.DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.RESET}` + ); + + return output.join('\n'); +} + +/** + * 行番号の差分を視覚的に表示(CODE_LOCATION_MISMATCH用) + * @param code コードの内容 + * @param expectedRange 期待される行範囲 + * @param actualRange 実際の行範囲 + * @returns 色付けされた行番号差分表示の文字列 + */ +export function displayLineRangeDiff( + code: string, + expectedRange: { start: number; end: number }, + actualRange: { start: number; end: number } +): string { + const output: string[] = []; + + // ヘッダー + output.push( + `${COLORS.DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.RESET}` + ); + output.push( + `${COLORS.RED}- 期待される行範囲: ${expectedRange.start}-${expectedRange.end}${COLORS.RESET}` + ); + output.push( + `${COLORS.GREEN}+ 実際の行範囲: ${actualRange.start}-${actualRange.end}${COLORS.RESET}` + ); + output.push( + `${COLORS.DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.RESET}` + ); + + // コードを表示(内容は同じなので、両方の行番号を表示) + const codeLines = code.split('\n'); + codeLines.forEach((line, index) => { + const expectedLineNum = expectedRange.start + index; + const actualLineNum = actualRange.start + index; + + // 期待される行番号と実際の行番号を並べて表示 + output.push( + `${COLORS.RED}${expectedLineNum.toString().padStart(4)}${COLORS.RESET} | ` + + `${COLORS.GREEN}${actualLineNum.toString().padStart(4)}${COLORS.RESET} | ` + + `${line}` + ); + }); + + output.push( + `${COLORS.DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.RESET}` + ); + + return output.join('\n'); +} + +/** + * 長いテキストを切り詰める(表示用) + * @param text テキスト + * @param maxLength 最大長 + * @returns 切り詰められたテキスト + */ +export function truncateText(text: string, maxLength: number): string { + if (text.length <= maxLength) { + return text; + } + return text.substring(0, maxLength) + '...'; +} diff --git a/src/utils/fix.ts b/src/utils/fix.ts new file mode 100644 index 0000000..d5b7c3b --- /dev/null +++ b/src/utils/fix.ts @@ -0,0 +1,660 @@ +/** + * 修正ロジックユーティリティ + */ + +import * as fs from 'fs'; +import * as path from 'path'; +import * as readline from 'readline'; + +import { expandMatchToScope } from './ast-scope-expansion'; +import { findSymbolInAST } from './ast-symbol-search'; +import { extractLinesFromFile, searchCodeInFileWithScopeExpansion } from './code-comparison'; +import { + findCodeBlockPosition, + insertCodeBlockAfterComment, + moveCodeRefCommentBeforeCodeBlock, + replaceCodeBlock, + replaceCodeRefComment, +} from './markdown-edit'; +import { askSelectOption } from './prompt'; +import type { CodeRefError, ExpandedMatch, FixAction } from './types'; + +/** + * エラーが修正可能かチェック + */ +export function isFixableError(error: CodeRefError): boolean { + return [ + 'CODE_LOCATION_MISMATCH', + 'CODE_BLOCK_MISSING', + 'CODE_CONTENT_MISMATCH', + 'LINE_OUT_OF_RANGE', + 'SYMBOL_RANGE_MISMATCH', + 'MULTIPLE_SYMBOLS_FOUND', + ].includes(error.type); +} + +/** + * CODE_LOCATION_MISMATCHの修正アクションを作成 + */ +export function createLocationMismatchFix(error: CodeRefError): FixAction { + if (!error.suggestedLines) { + throw new Error('CODE_LOCATION_MISMATCHにはsuggestedLinesが必要です'); + } + + const { ref } = error; + const oldComment = ref.fullMatch; + const newComment = ``; + + // 実際のコードを取得してプレビュー + const projectRoot = path.resolve(__dirname, '../../..'); + const absolutePath = path.resolve(projectRoot, ref.refPath); + const actualCode = extractLinesFromFile( + absolutePath, + error.suggestedLines.start, + error.suggestedLines.end + ); + + return { + type: 'UPDATE_LINE_NUMBERS', + error, + description: `行番号を ${ref.startLine}-${ref.endLine} から ${error.suggestedLines.start}-${error.suggestedLines.end} に更新`, + preview: `${oldComment}\n→ ${newComment}`, + newStartLine: error.suggestedLines.start, + newEndLine: error.suggestedLines.end, + newCodeBlock: actualCode, + }; +} + +/** + * CODE_REFコメント近くのコードブロックを検索 + * + * @param content ドキュメントの内容 + * @param commentMatch CODE_REFコメントのテキスト + * @returns コードブロックの情報、見つからない場合はnull + */ +function findCodeBlockNearComment( + content: string, + commentMatch: string +): { start: number; end: number; language: string; content: string } | null { + const commentIndex = content.indexOf(commentMatch); + if (commentIndex === -1) return null; + + const commentEnd = content.indexOf('-->', commentIndex); + if (commentEnd === -1) return null; + + const searchStart = commentEnd + 3; + const searchWindow = content.substring(searchStart, searchStart + 5000); + + // 次のCODE_REFコメントを検索 + const nextCommentMatch = searchWindow.match(/`; + + const option2: FixAction = { + type: 'UPDATE_LINE_NUMBERS', + error, + description: `シンボル指定を削除して行番号指定に変更(コードブロックは維持、手動調整が必要)`, + preview: `${oldComment}\n→ ${newComment}`, + newStartLine: symbolMatch.startLine, + newEndLine: symbolMatch.endLine, + newCodeBlock: ref.codeBlock, // 既存のコードブロックを維持 + }; + + return [option1, option2]; + } + } + + // 行番号指定がある場合の既存ロジック + if (ref.startLine === null || ref.endLine === null) { + throw new Error('CODE_CONTENT_MISMATCHには行番号指定またはシンボル指定が必要です'); + } + + const actualCode = extractLinesFromFile(absolutePath, ref.startLine, ref.endLine); + + // AST解析を使って、指定された行範囲が不完全なスコープ(関数の途中など)でないかチェック + // 開始行のみを使用してスコープを検出(範囲全体を使うと誤検出の可能性があるため) + const fileContent = fs.readFileSync(absolutePath, 'utf-8'); + const expandedMatches = expandMatchToScope({ + filePath: absolutePath, + originalMatch: { start: ref.startLine, end: ref.startLine }, + fileContent, + }); + + // 拡張結果が元の範囲と異なり、かつ高信頼度の場合は行番号も更新 + const expanded = expandedMatches[0]; + if ( + expanded && + expanded.confidence === 'high' && + (expanded.start !== ref.startLine || expanded.end !== ref.endLine) + ) { + // スコープ拡張が成功した場合は行番号も更新 + const expandedCode = extractLinesFromFile(absolutePath, expanded.start, expanded.end); + const scopeInfo = expanded.scopeType ? ` (${expanded.scopeType})` : ''; + console.log( + ` ℹ️ AST解析により ${ref.startLine}-${ref.endLine} を ${expanded.start}-${expanded.end} に拡張${scopeInfo}` + ); + + const oldComment = ref.fullMatch; + const newComment = ``; + + return { + type: 'UPDATE_LINE_NUMBERS', + error, + description: `AST解析により行番号を ${ref.startLine}-${ref.endLine} から ${expanded.start}-${expanded.end} に更新し、コードブロックを置換`, + preview: `${oldComment}\n→ ${newComment}`, + newStartLine: expanded.start, + newEndLine: expanded.end, + newCodeBlock: expandedCode, + }; + } + + // AST拡張が不要または失敗した場合は、従来通りコードブロックのみ置換 + const expectedPreview = error.expectedCode?.substring(0, 100) || ''; + const actualPreview = actualCode.substring(0, 100); + + return { + type: 'REPLACE_CODE_BLOCK', + error, + description: 'コードブロックを実際のコード内容で置換', + preview: `期待: ${expectedPreview}${expectedPreview.length >= 100 ? '...' : ''}\n実際: ${actualPreview}${actualPreview.length >= 100 ? '...' : ''}`, + newCodeBlock: actualCode, + }; +} + +/** + * LINE_OUT_OF_RANGEの修正アクションを作成 + */ +export function createLineOutOfRangeFix(error: CodeRefError): FixAction { + const { ref } = error; + + // エラーメッセージから行数を抽出 + const match = error.message.match(/(\d+)\s*>\s*(\d+)/); + if (!match) { + throw new Error('LINE_OUT_OF_RANGEエラーメッセージから行数を取得できません'); + } + + const totalLines = parseInt(match[2], 10); + const oldComment = ref.fullMatch; + const newComment = ``; + + return { + type: 'UPDATE_END_LINE', + error, + description: `終了行を ${ref.endLine} から ${totalLines} (ファイル末尾) に修正`, + preview: `${oldComment}\n→ ${newComment}`, + newStartLine: ref.startLine!, + newEndLine: totalLines, + }; +} + +/** + * SYMBOL_RANGE_MISMATCHの修正アクションを作成 + */ +export function createSymbolRangeMismatchFix(error: CodeRefError): FixAction { + if (!error.suggestedSymbol) { + throw new Error('SYMBOL_RANGE_MISMATCHにはsuggestedSymbolが必要です'); + } + + const { ref } = error; + const suggested = error.suggestedSymbol; + + const oldComment = ref.fullMatch; + const newComment = ``; + + return { + type: 'UPDATE_SYMBOL_RANGE', + error, + description: `シンボル "${ref.symbolPath}" の行番号を ${ref.startLine}-${ref.endLine} から ${suggested.startLine}-${suggested.endLine} に更新`, + preview: `${oldComment}\n→ ${newComment}`, + newStartLine: suggested.startLine, + newEndLine: suggested.endLine, + }; +} + +/** + * MULTIPLE_SYMBOLS_FOUNDの修正アクションを作成(対話的に選択) + */ +export async function createMultipleSymbolsFoundFix( + error: CodeRefError, + rl: readline.Interface +): Promise { + if (!error.foundSymbols || error.foundSymbols.length === 0) { + throw new Error('MULTIPLE_SYMBOLS_FOUNDにはfoundSymbolsが必要です'); + } + + const { ref } = error; + + const options = error.foundSymbols.map((symbol) => { + const classInfo = symbol.className ? `${symbol.className}#` : ''; + return `行 ${symbol.startLine}-${symbol.endLine} (${classInfo}${symbol.memberName})`; + }); + + const selectedIndex = await askSelectOption( + rl, + options, + `⚠️ シンボル "${ref.symbolPath}" が${error.foundSymbols.length}箇所で見つかりました。どの位置を使用しますか?` + ); + + const selected = error.foundSymbols[selectedIndex]; + + const oldComment = ref.fullMatch; + const newComment = ``; + + return { + type: 'UPDATE_SYMBOL_RANGE', + error, + description: `シンボル "${ref.symbolPath}" の行番号を追加: ${selected.startLine}-${selected.endLine}`, + preview: `${oldComment}\n→ ${newComment}`, + newStartLine: selected.startLine, + newEndLine: selected.endLine, + }; +} + +/** + * エラータイプに基づいて修正アクションを作成 + */ +export async function createFixAction( + error: CodeRefError, + rl?: readline.Interface +): Promise { + if (!isFixableError(error)) { + return null; + } + + switch (error.type) { + case 'CODE_LOCATION_MISMATCH': + return createLocationMismatchFix(error); + case 'CODE_BLOCK_MISSING': + return createBlockMissingFix(error); + case 'CODE_CONTENT_MISMATCH': + return createContentMismatchFix(error); + case 'LINE_OUT_OF_RANGE': + return createLineOutOfRangeFix(error); + case 'SYMBOL_RANGE_MISMATCH': + return createSymbolRangeMismatchFix(error); + case 'MULTIPLE_SYMBOLS_FOUND': + if (!rl) { + throw new Error('MULTIPLE_SYMBOLS_FOUNDにはreadline.Interfaceが必要です'); + } + return await createMultipleSymbolsFoundFix(error, rl); + default: + return null; + } +} + +/** + * マークダウンファイルに修正を適用 + * @returns ライン delta(正の数は行追加、負の数は行削除、0は行数変化なし) + */ +export function applyFix(action: FixAction): number { + const filePath = action.error.ref.docFile; + let content = fs.readFileSync(filePath, 'utf-8'); + const originalLines = content.split('\n').length; + + switch (action.type) { + case 'UPDATE_LINE_NUMBERS': + case 'UPDATE_END_LINE': { + // CODE_REFコメントを更新 + const oldComment = action.error.ref.fullMatch; + const newComment = ``; + content = replaceCodeRefComment(content, oldComment, newComment); + + // コードブロックも更新(存在する場合) + if (action.newCodeBlock) { + const blockPos = findCodeBlockPosition(content, newComment); + if (blockPos) { + const newBlock = `\`\`\`${blockPos.language}\n${action.newCodeBlock}\n\`\`\``; + content = + content.substring(0, blockPos.start) + newBlock + content.substring(blockPos.end); + } + } + break; + } + + case 'INSERT_CODE_BLOCK': { + content = insertCodeBlockAfterComment( + content, + action.error.ref.fullMatch, + action.newCodeBlock! + ); + break; + } + + case 'REPLACE_CODE_BLOCK': { + if (!action.error.ref.codeBlock) { + throw new Error('コードブロックが見つかりません'); + } + content = replaceCodeBlock(content, action.error.ref.codeBlock, action.newCodeBlock!); + break; + } + + case 'MOVE_CODE_REF_COMMENT': { + if (!action.codeBlockPosition) { + throw new Error('MOVE_CODE_REF_COMMENTにはcodeBlockPositionが必要です'); + } + + content = moveCodeRefCommentBeforeCodeBlock( + content, + action.error.ref.fullMatch, + action.codeBlockPosition.start + ); + break; + } + } + + fs.writeFileSync(filePath, content, 'utf-8'); + + // ライン deltaを返す + const newLines = content.split('\n').length; + return newLines - originalLines; +} + +/** + * 優先順位付けのコンテキスト + */ +interface PrioritizationContext { + originalStart: number; + originalEnd: number; +} + +/** + * 信頼度とスコープタイプを考慮した優先順位付け + * + * @param matches 拡張されたマッチの配列 + * @param context 優先順位付けのコンテキスト(元の行番号) + * @returns 優先順位付けされたマッチ(信頼度・スコープタイプ考慮) + */ +function prioritizeMatchesWithConfidence( + matches: ExpandedMatch[], + context: PrioritizationContext +): ExpandedMatch[] { + return matches.sort((a, b) => { + // 1. 信頼度で優先(最優先) + const confidenceScore = { high: 3, medium: 2, low: 1 }; + const confDiff = confidenceScore[b.confidence] - confidenceScore[a.confidence]; + if (confDiff !== 0) return confDiff; + + // 2. 元の行番号との近接度で優先 + const distanceA = Math.abs(a.start - context.originalStart); + const distanceB = Math.abs(b.start - context.originalStart); + const distDiff = distanceA - distanceB; + if (distDiff !== 0) return distDiff; + + // 3. スコープタイプの優先順位 + const scopePriority = { + interface: 5, + type: 4, + class: 3, + function: 2, + const: 1, + unknown: 0, + }; + const scopeDiff = + scopePriority[b.scopeType || 'unknown'] - scopePriority[a.scopeType || 'unknown']; + if (scopeDiff !== 0) return scopeDiff; + + // 4. 短い範囲を優先(より具体的なマッチ) + return a.end - a.start - (b.end - b.start); + }); +} + +/** + * 複数マッチの処理 + */ +export async function handleMultipleMatches( + error: CodeRefError, + rl: readline.Interface +): Promise { + const { ref } = error; + + if (!ref.codeBlock) { + return null; + } + + const projectRoot = path.resolve(__dirname, '../../..'); + const absolutePath = path.resolve(projectRoot, ref.refPath); + + // AST解析によるスコープ拡張版を使用 + const matches = searchCodeInFileWithScopeExpansion(absolutePath, ref.codeBlock); + + if (matches.length === 0) { + return null; + } + + if (matches.length === 1) { + // 単一マッチ - 修正アクションを作成 + const match = matches[0]; + const confidenceInfo = + match.confidence === 'high' && match.scopeType + ? ` (${match.scopeType})` + : match.confidence === 'low' + ? ' (低信頼度)' + : ''; + console.log( + `\n✓ ${ref.refPath} で単一マッチを検出: 行 ${match.start}-${match.end}${confidenceInfo}` + ); + return { + type: 'UPDATE_LINE_NUMBERS', + error, + description: `行番号を ${ref.startLine}-${ref.endLine} から ${match.start}-${match.end} に更新`, + preview: ``, + newStartLine: match.start, + newEndLine: match.end, + matchOptions: matches.map((m) => ({ start: m.start, end: m.end })), + }; + } + + // 複数マッチ - 優先順位付けして自動選択を試みる + const sortedMatches = prioritizeMatchesWithConfidence(matches, { + originalStart: ref.startLine!, + originalEnd: ref.endLine!, + }); + + // 信頼度が高いマッチが1つだけなら自動選択 + const highConfidenceMatches = sortedMatches.filter((m) => m.confidence === 'high'); + if (highConfidenceMatches.length === 1) { + const bestMatch = highConfidenceMatches[0]; + const scopeInfo = bestMatch.scopeType ? ` (${bestMatch.scopeType})` : ''; + console.log( + `\n✓ ${ref.refPath} で最も適切なマッチを自動選択: 行 ${bestMatch.start}-${bestMatch.end}${scopeInfo}` + ); + return { + type: 'UPDATE_LINE_NUMBERS', + error, + description: `行番号を ${ref.startLine}-${ref.endLine} から ${bestMatch.start}-${bestMatch.end} に更新`, + preview: ``, + newStartLine: bestMatch.start, + newEndLine: bestMatch.end, + matchOptions: matches.map((m) => ({ start: m.start, end: m.end })), + }; + } + + // それ以外はユーザーに選択させる + console.log(`\n⚠️ ${ref.refPath} でコードが ${matches.length} 箇所見つかりました:`); + const options = sortedMatches.map((m) => { + const confidenceLabel = + m.confidence === 'high' ? '高' : m.confidence === 'medium' ? '中' : '低'; + const scopeInfo = m.scopeType && m.scopeType !== 'unknown' ? `, ${m.scopeType}` : ''; + return `行 ${m.start}-${m.end} (信頼度: ${confidenceLabel}${scopeInfo})`; + }); + + const selection = await askSelectOption(rl, options, 'どの位置を使用しますか?'); + const selectedMatch = sortedMatches[selection]; + + // 選択されたマッチの信頼度が低い場合は警告 + if (selectedMatch.confidence === 'low') { + console.warn('⚠️ スコープ検出の信頼度が低いため、結果を確認してください。'); + } + if (selectedMatch.expansionType === 'none') { + console.warn('⚠️ 構造的な解析ができなかったため、単純な文字列マッチングを使用しています。'); + } + + return { + type: 'UPDATE_LINE_NUMBERS', + error, + description: `行番号を ${ref.startLine}-${ref.endLine} から ${selectedMatch.start}-${selectedMatch.end} に更新`, + preview: ``, + newStartLine: selectedMatch.start, + newEndLine: selectedMatch.end, + matchOptions: matches.map((m) => ({ start: m.start, end: m.end })), + }; +} diff --git a/src/utils/ignore-pattern.ts b/src/utils/ignore-pattern.ts new file mode 100644 index 0000000..20696dc --- /dev/null +++ b/src/utils/ignore-pattern.ts @@ -0,0 +1,66 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * ファイルパスがパターンにマッチするかチェック(シンプルなグロブマッチング) + */ +export function matchesPattern(filePath: string, pattern: string): boolean { + // ディレクトリパターン(末尾が/) + if (pattern.endsWith('/')) { + return filePath.startsWith(pattern) || filePath.startsWith(pattern.slice(0, -1) + '/'); + } + + // **/ パターン(任意のディレクトリ階層) + if (pattern.startsWith('**/')) { + const suffix = pattern.slice(3); + // ワイルドカード処理 + if (suffix.includes('*') || suffix.includes('?')) { + const regex = new RegExp( + suffix.replace(/\./g, '\\.').replace(/\*/g, '.*').replace(/\?/g, '.') + ); + // ファイル名全体またはパスの各部分でマッチングを試みる + const basename = path.basename(filePath); + const pathParts = filePath.split('/'); + return regex.test(basename) || pathParts.some((part) => regex.test(part)); + } + return filePath.endsWith(suffix) || filePath.includes('/' + suffix); + } + + // * または ? ワイルドカード(パスセパレーター無しの場合のみベース名でマッチング) + if (pattern.includes('*') || pattern.includes('?')) { + const regex = new RegExp( + '^' + pattern.replace(/\./g, '\\.').replace(/\*/g, '.*').replace(/\?/g, '.') + '$' + ); + // パスセパレーターが無いパターンの場合はベース名のみでマッチング + if (!pattern.includes('/')) { + return regex.test(path.basename(filePath)); + } + // パスセパレーターがある場合はフルパスでマッチング + return regex.test(filePath); + } + + // 完全一致またはプレフィックスマッチ + return filePath === pattern || filePath.startsWith(pattern + '/'); +} + +/** + * ファイルが指定されたパターンで除外されるかチェック + */ +export function isIgnored(filePath: string, patterns: string[]): boolean { + return patterns.some((pattern) => matchesPattern(filePath, pattern)); +} + +/** + * .docsignoreファイルを読み込んでパターンを取得 + */ +export function loadDocsignorePatterns(docsignoreFile: string): string[] { + if (!fs.existsSync(docsignoreFile)) { + return []; + } + + const content = fs.readFileSync(docsignoreFile, 'utf-8'); + return content + .split('\n') + .map((line) => line.trim()) + .filter((line) => line && !line.startsWith('#')); // コメントと空行を除外 +} diff --git a/src/utils/markdown-edit.ts b/src/utils/markdown-edit.ts new file mode 100644 index 0000000..4d166bd --- /dev/null +++ b/src/utils/markdown-edit.ts @@ -0,0 +1,190 @@ +/** + * マークダウン編集ユーティリティ + */ + +import { normalizeCode } from './markdown'; + +/** + * CODE_REFコメントを置換 + */ +export function replaceCodeRefComment( + content: string, + oldComment: string, + newComment: string +): string { + // 安全のため完全一致を使用 + const index = content.indexOf(oldComment); + if (index === -1) { + throw new Error(`CODE_REFコメントが見つかりません: ${oldComment}`); + } + + return content.substring(0, index) + newComment + content.substring(index + oldComment.length); +} + +/** + * CODE_REFコメントの後にコードブロックを挿入 + */ +export function insertCodeBlockAfterComment( + content: string, + commentMatch: string, + codeBlock: string, + language = 'typescript' +): string { + const commentIndex = content.indexOf(commentMatch); + if (commentIndex === -1) { + throw new Error(`CODE_REFコメントが見つかりません: ${commentMatch}`); + } + + // コメントの終了(-->)を検索 + const commentEnd = content.indexOf('-->', commentIndex); + if (commentEnd === -1) { + throw new Error('CODE_REFコメントの終了タグが見つかりません'); + } + + // コメント後に挿入、次の行にスキップ + const insertPosition = commentEnd + 3; + + // コメント後に既にコンテンツがあるかチェック + const afterComment = content.substring(insertPosition, insertPosition + 10); + const newlinePrefix = afterComment.startsWith('\n') ? '\n' : '\n\n'; + + const codeBlockText = `${newlinePrefix}\`\`\`${language}\n${codeBlock}\n\`\`\`\n`; + + return content.substring(0, insertPosition) + codeBlockText + content.substring(insertPosition); +} + +/** + * マークダウン内のコードブロックを置換 + */ +export function replaceCodeBlock( + content: string, + oldCodeBlock: string, + newCodeBlock: string +): string { + // マークダウン形式でコードブロックを検索 + const codeBlockPattern = /```[\w]*\n([\s\S]*?)```/g; + let match: RegExpExecArray | null; + let found = false; + + while ((match = codeBlockPattern.exec(content)) !== null) { + const blockContent = match[1]; + + // 正規化して比較 + if (normalizeCode(blockContent) === normalizeCode(oldCodeBlock)) { + // このブロックを置換 + const startIndex = match.index; + const endIndex = startIndex + match[0].length; + + // 言語識別子を抽出 + const langMatch = match[0].match(/```([\w]*)\n/); + const language = langMatch ? langMatch[1] : ''; + + const newBlock = `\`\`\`${language}\n${newCodeBlock}\n\`\`\``; + + content = content.substring(0, startIndex) + newBlock + content.substring(endIndex); + found = true; + break; + } + } + + if (!found) { + throw new Error('一致するコードブロックが見つかりません'); + } + + return content; +} + +/** + * CODE_REFコメント後のコードブロック位置を検索 + */ +export function findCodeBlockPosition( + content: string, + commentMatch: string +): { start: number; end: number; language: string } | null { + const commentIndex = content.indexOf(commentMatch); + if (commentIndex === -1) { + return null; + } + + const commentEnd = content.indexOf('-->', commentIndex); + if (commentEnd === -1) { + return null; + } + + // コメント後500文字以内でコードブロックを検索 + const searchStart = commentEnd + 3; + const searchWindow = content.substring(searchStart, searchStart + 500); + + const codeBlockMatch = searchWindow.match(/```([\w]*)\n([\s\S]*?)```/); + if (!codeBlockMatch) { + return null; + } + + return { + start: searchStart + codeBlockMatch.index!, + end: searchStart + codeBlockMatch.index! + codeBlockMatch[0].length, + language: codeBlockMatch[1] || 'typescript', + }; +} + +/** + * CODE_REFコメントをコードブロックの直前に移動 + * + * @param content マークダウンファイルの内容 + * @param commentMatch CODE_REFコメントのテキスト + * @param codeBlockStart コードブロックの開始位置 + * @returns 修正後のマークダウン内容 + */ +export function moveCodeRefCommentBeforeCodeBlock( + content: string, + commentMatch: string, + codeBlockStart: number +): string { + // 1. CODE_REFコメントの位置を特定 + const commentIndex = content.indexOf(commentMatch); + if (commentIndex === -1) { + throw new Error(`CODE_REFコメントが見つかりません: ${commentMatch}`); + } + + const commentEnd = content.indexOf('-->', commentIndex); + if (commentEnd === -1) { + throw new Error('CODE_REFコメントの終了タグが見つかりません'); + } + + // 2. 削除範囲を決定(前後の改行を含める) + let removalStart = commentIndex; + let removalEnd = commentEnd + 3; + + // コメントの前に改行がある場合、それも削除 + if (commentIndex > 0 && content[commentIndex - 1] === '\n') { + removalStart = commentIndex - 1; + } + + // コメントの後の改行を削除(最大2つ) + let newlinesAfter = 0; + while (removalEnd < content.length && content[removalEnd] === '\n' && newlinesAfter < 2) { + removalEnd++; + newlinesAfter++; + } + + // 3. コメントを削除 + const contentWithoutComment = content.substring(0, removalStart) + content.substring(removalEnd); + + // 4. 位置調整(削除によるシフト) + const removedLength = removalEnd - removalStart; + const adjustedCodeBlockStart = + codeBlockStart > commentIndex ? codeBlockStart - removedLength : codeBlockStart; + + // 5. コードブロックの直前に挿入 + const beforeCodeBlock = contentWithoutComment.substring(0, adjustedCodeBlockStart); + const afterCodeBlock = contentWithoutComment.substring(adjustedCodeBlockStart); + + // コメントの前に適切な改行を追加 + const needsNewlineBefore = beforeCodeBlock.length > 0 && !beforeCodeBlock.endsWith('\n\n'); + const prefix = needsNewlineBefore ? '\n' : ''; + + // コメントとコードブロックの間に1つの改行 + const commentWithNewline = `${prefix}${commentMatch}\n`; + + return beforeCodeBlock + commentWithNewline + afterCodeBlock; +} diff --git a/src/utils/markdown.ts b/src/utils/markdown.ts new file mode 100644 index 0000000..beec2c0 --- /dev/null +++ b/src/utils/markdown.ts @@ -0,0 +1,105 @@ +/** + * マークダウンドキュメント処理ユーティリティ + */ + +import type { CodeRef } from './types'; + +/** + * CODE_REFコメントの後にあるコードブロックを抽出する + * + * @param content マークダウンファイルの内容 + * @param commentIndex CODE_REFコメントの開始位置 + * @returns コードブロックの内容、見つからない場合はnull + * + * **厳格なルール**: CODE_REFコメントとコードブロックの間に、空行以外のテキスト + * (見出し、段落、リストなど)がある場合は、コードブロックを関連付けません。 + */ +export function extractCodeBlockAfterComment(content: string, commentIndex: number): string | null { + // コメント終了位置(-->)を見つける + const commentEndMarker = '-->'; + const commentEnd = content.indexOf(commentEndMarker, commentIndex); + + if (commentEnd === -1) { + return null; + } + + // コメント終了後から5000文字以内でコードブロックを検索 + const searchStart = commentEnd + commentEndMarker.length; + const searchWindow = content.substring(searchStart, searchStart + 5000); + + // コードブロックのパターン: ```language\ncode\n``` + // 言語識別子はオプション(typescript, javascript, など) + // 言語識別子の後に空白があってもマッチする + const codeBlockPattern = /```[\w]*\s*\n([\s\S]*?)```/; + const match = searchWindow.match(codeBlockPattern); + + if (!match) { + return null; + } + + // コードブロックの開始位置を取得 + const codeBlockStart = match.index!; + + // コメント終了後からコードブロック開始までの間のテキストを取得 + const textBetween = searchWindow.substring(0, codeBlockStart); + + // 空行以外のテキストがあるかチェック + // 空行、空白のみの行は許容する + const hasNonEmptyContent = textBetween.split('\n').some((line) => line.trim() !== ''); + + if (hasNonEmptyContent) { + // コメントとコードブロックの間に他のテキストがある場合はnullを返す + return null; + } + + return match[1]; +} + +/** + * CODE_REFの配列にコードブロックを関連付ける + * + * @param content マークダウンファイルの内容 + * @param refs CODE_REFの配列 + * @returns コードブロックが関連付けられたCODE_REFの配列 + */ +export function associateCodeBlocksWithRefs(content: string, refs: CodeRef[]): CodeRef[] { + return refs.map((ref) => { + // codeBlockStartOffsetが設定されている場合はそれを使用、なければfullMatchから検索 + const commentIndex = + ref.codeBlockStartOffset !== undefined + ? ref.codeBlockStartOffset + : content.indexOf(ref.fullMatch); + + if (commentIndex === -1) { + // fullMatchが見つからない場合(通常は発生しないはず) + return ref; + } + + // コードブロックを抽出 + const codeBlock = extractCodeBlockAfterComment(content, commentIndex); + + if (codeBlock !== null) { + return { + ...ref, + codeBlock, + codeBlockStartOffset: commentIndex, + }; + } + + return ref; + }); +} + +/** + * コードを正規化する(空白の違いを吸収) + * + * すべての空白文字を削除することで、インデント、改行位置、 + * スペースの数などの違いを完全に無視します。 + * + * @param code 正規化するコード + * @returns 正規化されたコード(空白なし) + */ +export function normalizeCode(code: string): string { + // すべての空白文字(改行、タブ、スペースなど)を削除 + return code.replace(/\s+/g, ''); +} diff --git a/src/utils/prompt.ts b/src/utils/prompt.ts new file mode 100644 index 0000000..0b6521c --- /dev/null +++ b/src/utils/prompt.ts @@ -0,0 +1,191 @@ +/** + * 対話的CLIプロンプトユーティリティ + */ + +import * as fs from 'fs'; +import * as path from 'path'; +import * as readline from 'readline'; + +import { extractLinesFromFile } from './code-comparison'; +import { displayCodeDiff, displayLineRangeDiff } from './diff-display'; +import type { FixAction } from './types'; + +/** + * Readlineインターフェースを作成 + */ +export function createPromptInterface(): readline.Interface { + return readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); +} + +/** + * Yes/No質問 + */ +export async function askYesNo( + rl: readline.Interface, + question: string, + defaultValue = false +): Promise { + const defaultText = defaultValue ? 'Y/n' : 'y/N'; + const answer = await askQuestion(rl, `${question} (${defaultText}): `); + + if (!answer.trim()) { + return defaultValue; + } + + return answer.toLowerCase().startsWith('y'); +} + +/** + * 質問して回答を取得 + */ +export function askQuestion(rl: readline.Interface, question: string): Promise { + return new Promise((resolve) => { + rl.question(question, (answer) => { + resolve(answer); + }); + }); +} + +/** + * 複数のオプションから選択 + * @returns 選択されたオプションのインデックス(0-based) + */ +export async function askSelectOption( + rl: readline.Interface, + options: string[], + message: string +): Promise { + console.log(`\n${message}`); + options.forEach((opt, idx) => { + console.log(` ${idx + 1}) ${opt}`); + }); + + while (true) { + const answer = await askQuestion(rl, `選択してください (1-${options.length}): `); + + const selection = parseInt(answer.trim(), 10); + if (selection >= 1 && selection <= options.length) { + return selection - 1; + } + + console.log('❌ 無効な選択です。もう一度入力してください。'); + } +} + +/** + * 修正プレビューを表示 + */ +export function displayFixPreview(action: FixAction): void { + console.log('\n変更内容:'); + console.log(`- 説明: ${action.description}`); + + // エラータイプに応じて色付き差分を表示 + const { error } = action; + const projectRoot = path.resolve(__dirname, '../../..'); + const absolutePath = path.resolve(projectRoot, error.ref.refPath); + + switch (error.type) { + case 'CODE_LOCATION_MISMATCH': { + // 行番号の差分を表示 + if (error.ref.codeBlock && action.newStartLine && action.newEndLine) { + // コードブロックが存在する場合、実際のコードを取得 + const actualCode = extractLinesFromFile( + absolutePath, + action.newStartLine, + action.newEndLine + ); + + const diff = displayLineRangeDiff( + actualCode, + { + start: error.ref.startLine!, + end: error.ref.endLine!, + }, + { + start: action.newStartLine, + end: action.newEndLine, + } + ); + console.log(diff); + } else { + // コードブロックがない場合はシンプルなプレビュー + console.log(action.preview); + } + break; + } + + case 'CODE_CONTENT_MISMATCH': { + // コード内容の差分を表示 + if (error.expectedCode && action.newCodeBlock) { + const diff = displayCodeDiff(error.expectedCode, action.newCodeBlock); + console.log(diff); + } else { + console.log(action.preview); + } + break; + } + + case 'INSERT_CODE_BLOCK': { + // 新規挿入の場合、挿入されるコードをシンプルに表示 + if (action.newCodeBlock) { + console.log('\x1b[32m+ コードブロックを挿入:\x1b[0m'); + console.log('\x1b[2m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m'); + const lines = action.newCodeBlock.split('\n'); + lines.forEach((line) => { + console.log(`\x1b[32m+ ${line}\x1b[0m`); + }); + console.log('\x1b[2m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m'); + } else { + console.log(action.preview); + } + break; + } + + case 'REPLACE_CODE_BLOCK': { + // コードブロックの置換の場合、CODE_CONTENT_MISMATCHと同じ処理 + if (error.expectedCode && action.newCodeBlock) { + const diff = displayCodeDiff(error.expectedCode, action.newCodeBlock); + console.log(diff); + } else { + console.log(action.preview); + } + break; + } + + case 'UPDATE_LINE_NUMBERS': + case 'UPDATE_END_LINE': { + // 行番号更新の場合、コメントの変更を表示 + const oldComment = error.ref.fullMatch; + const newComment = ``; + + console.log('\x1b[2m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m'); + console.log(`\x1b[31m- ${oldComment}\x1b[0m`); + console.log(`\x1b[32m+ ${newComment}\x1b[0m`); + console.log('\x1b[2m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m'); + + // コードブロックも存在する場合は内容も表示 + if (action.newCodeBlock && fs.existsSync(absolutePath)) { + console.log('\nコードブロック内容:'); + const lines = action.newCodeBlock.split('\n').slice(0, 10); // 最初の10行 + lines.forEach((line) => { + console.log(` ${line}`); + }); + if (action.newCodeBlock.split('\n').length > 10) { + console.log(' ...'); + } + } + break; + } + + default: { + // その他の場合は従来のプレビューを表示 + if (action.preview) { + console.log(action.preview); + } + break; + } + } +} diff --git a/src/utils/types.ts b/src/utils/types.ts new file mode 100644 index 0000000..52c3b86 --- /dev/null +++ b/src/utils/types.ts @@ -0,0 +1,143 @@ +/** + * スクリプト共通の型定義 + */ + +/** + * コード参照情報 + */ +export interface CodeRef { + fullMatch: string; + refPath: string; + startLine: number | null; + endLine: number | null; + docFile: string; + docLineNumber?: number; // ドキュメント内のCODE_REFコメントの行番号 + codeBlock?: string; // ドキュメント内のコードブロック内容 + codeBlockStartOffset?: number; // コードブロックの開始位置 + // シンボル指定機能の追加フィールド + symbolPath?: string; // "functionName" or "ClassName#methodName" + className?: string; // "ClassName" (if specified) + memberName?: string; // "methodName" or "functionName" +} + +/** + * コード参照エラー + */ +export interface CodeRefError { + type: + | 'FILE_NOT_FOUND' + | 'LINE_OUT_OF_RANGE' + | 'INVALID_LINE_NUMBER' + | 'INVALID_RANGE' + | 'READ_ERROR' + | 'PATH_TRAVERSAL' + | 'CODE_CONTENT_MISMATCH' // 指定行のコードが異なる + | 'CODE_LOCATION_MISMATCH' // コードは一致するが行数が異なる + | 'CODE_BLOCK_MISSING' // CODE_REFの後にコードブロックがない + | 'INSERT_CODE_BLOCK' // CODE_REFの後にコードブロックを挿入する必要がある + | 'REPLACE_CODE_BLOCK' // CODE_REFの後のコードブロックを置換する必要がある + | 'UPDATE_LINE_NUMBERS' // CODE_REFの行番号を更新する必要がある + | 'UPDATE_END_LINE' // CODE_REFの終了行番号を更新する必要がある + | 'SYMBOL_NOT_FOUND' // 指定されたシンボルが見つからない + | 'MULTIPLE_SYMBOLS_FOUND' // 同名のシンボルが複数存在 + | 'SYMBOL_RANGE_MISMATCH' // シンボルの範囲と指定行番号が一致しない + | 'NOT_TYPESCRIPT_FILE'; // TypeScript/JavaScriptファイル以外 + message: string; + ref: CodeRef; + suggestedLines?: { start: number; end: number }; // CODE_LOCATION_MISMATCH用 + actualCode?: string; // CODE_CONTENT_MISMATCH用 + expectedCode?: string; // CODE_CONTENT_MISMATCH用 + // シンボル指定機能の追加フィールド + foundSymbols?: SymbolMatch[]; // MULTIPLE_SYMBOLS_FOUND用 + suggestedSymbol?: SymbolMatch; // SYMBOL_RANGE_MISMATCH用 +} + +/** + * Git実行オプション + */ +export interface GitExecOptions { + cwd?: string; + encoding?: BufferEncoding; +} + +/** + * パターンマッチング結果 + */ +export interface PatternMatchResult { + matched: boolean; + pattern?: string; +} + +/** + * 修正操作タイプ + */ +export type FixType = + | 'UPDATE_LINE_NUMBERS' // CODE_LOCATION_MISMATCH + | 'INSERT_CODE_BLOCK' // CODE_BLOCK_MISSING + | 'REPLACE_CODE_BLOCK' // CODE_CONTENT_MISMATCH + | 'UPDATE_END_LINE' // LINE_OUT_OF_RANGE + | 'UPDATE_SYMBOL_RANGE' // SYMBOL_RANGE_MISMATCH + | 'MOVE_CODE_REF_COMMENT'; // CODE_BLOCK_MISSING (with existing block) + +/** + * 修正アクション詳細 + */ +export interface FixAction { + type: FixType; + error: CodeRefError; + description: string; + preview: string; + newStartLine?: number; + newEndLine?: number; + newCodeBlock?: string; + matchOptions?: Array<{ start: number; end: number }>; + codeBlockPosition?: { + start: number; + end: number; + language: string; + content: string; + }; +} + +/** + * 修正結果 + */ +export interface FixResult { + success: boolean; + action: FixAction; + error?: string; + backupPath?: string; +} + +/** + * CLIオプション + */ +export interface FixOptions { + dryRun: boolean; + auto: boolean; + noBackup: boolean; + verbose: boolean; +} + +/** + * スコープ拡張されたマッチ情報 + */ +export interface ExpandedMatch { + start: number; + end: number; + confidence: 'high' | 'medium' | 'low'; + expansionType?: 'ast' | 'heuristic' | 'none'; + scopeType?: 'function' | 'class' | 'interface' | 'type' | 'const' | 'unknown'; +} + +/** + * シンボル検索のマッチ情報 + */ +export interface SymbolMatch { + className?: string; // クラス名(クラスメソッドの場合) + memberName: string; // メソッド名または関数名 + startLine: number; // 開始行番号(JSDocコメント含む) + endLine: number; // 終了行番号 + scopeType: 'function' | 'class' | 'method'; // スコープタイプ + confidence: 'high' | 'medium' | 'low'; // 信頼度 +} diff --git a/test/ast-symbol-search.test.ts b/test/ast-symbol-search.test.ts new file mode 100644 index 0000000..1cc4f11 --- /dev/null +++ b/test/ast-symbol-search.test.ts @@ -0,0 +1,311 @@ +import { + clearASTCache, + findSymbolInAST, + isTypeScriptOrJavaScript, + parseSymbolPath, + selectBestSymbolMatch, +} from '../src/utils/ast-symbol-search'; +import type { SymbolMatch } from '../src/utils/types'; + +describe('ast-symbol-search', () => { + afterEach(() => { + clearASTCache(); + }); + + describe('isTypeScriptOrJavaScript', () => { + it('TypeScript/JavaScriptファイルを正しく判定すること', () => { + expect(isTypeScriptOrJavaScript('/path/to/file.ts')).toBe(true); + expect(isTypeScriptOrJavaScript('/path/to/file.tsx')).toBe(true); + expect(isTypeScriptOrJavaScript('/path/to/file.js')).toBe(true); + expect(isTypeScriptOrJavaScript('/path/to/file.jsx')).toBe(true); + expect(isTypeScriptOrJavaScript('/path/to/file.mjs')).toBe(true); + expect(isTypeScriptOrJavaScript('/path/to/file.cjs')).toBe(true); + }); + + it('TypeScript/JavaScript以外のファイルを拒否すること', () => { + expect(isTypeScriptOrJavaScript('/path/to/file.py')).toBe(false); + expect(isTypeScriptOrJavaScript('/path/to/file.java')).toBe(false); + expect(isTypeScriptOrJavaScript('/path/to/file.md')).toBe(false); + expect(isTypeScriptOrJavaScript('/path/to/file.json')).toBe(false); + }); + }); + + describe('parseSymbolPath', () => { + it('関数名のみのシンボルパスをパースすること', () => { + const result = parseSymbolPath('functionName'); + expect(result).toEqual({ + memberName: 'functionName', + }); + }); + + it('クラス名+メソッド名のシンボルパスをパースすること', () => { + const result = parseSymbolPath('ClassName#methodName'); + expect(result).toEqual({ + className: 'ClassName', + memberName: 'methodName', + }); + }); + + it('空白を含むシンボルパスをトリムすること', () => { + const result = parseSymbolPath(' ClassName # methodName '); + expect(result).toEqual({ + className: 'ClassName', + memberName: 'methodName', + }); + }); + + it('無効なシンボルパスでエラーをスローすること', () => { + expect(() => parseSymbolPath('ClassName#method#extra')).toThrow('Invalid symbol path'); + }); + }); + + describe('findSymbolInAST', () => { + it('クラスのメソッドを検索すること', () => { + const fileContent = ` + export class TestClass { + /** + * Test method + */ + testMethod() { + console.log('test'); + } + } + `; + + const matches = findSymbolInAST(fileContent, '/test/file.ts', { + className: 'TestClass', + memberName: 'testMethod', + }); + + expect(matches).toHaveLength(1); + expect(matches[0]).toMatchObject({ + className: 'TestClass', + memberName: 'testMethod', + scopeType: 'method', + confidence: 'high', + }); + // JSDocコメントを含む行番号を検証 + expect(matches[0].startLine).toBeLessThan(matches[0].endLine); + }); + + it('トップレベル関数を検索すること', () => { + const fileContent = ` + /** + * Top level function + */ + export function topLevelFunction() { + return 'test'; + } + `; + + const matches = findSymbolInAST(fileContent, '/test/file.ts', { + memberName: 'topLevelFunction', + }); + + expect(matches).toHaveLength(1); + expect(matches[0]).toMatchObject({ + memberName: 'topLevelFunction', + scopeType: 'function', + confidence: 'high', + }); + expect(matches[0].className).toBeUndefined(); + }); + + it('複数のマッチを返すこと(オーバーロード)', () => { + const fileContent = ` + export class TestClass { + testMethod(arg1: string): void; + testMethod(arg1: string, arg2: number): void; + testMethod(arg1: string, arg2?: number): void { + console.log(arg1, arg2); + } + } + `; + + const matches = findSymbolInAST(fileContent, '/test/file.ts', { + className: 'TestClass', + memberName: 'testMethod', + }); + + // オーバーロードシグネチャは検出されず、実装のみ検出される + expect(matches).toHaveLength(1); + }); + + it('シンボルが見つからない場合は空配列を返すこと', () => { + const fileContent = ` + export class TestClass { + otherMethod() { + console.log('test'); + } + } + `; + + const matches = findSymbolInAST(fileContent, '/test/file.ts', { + className: 'TestClass', + memberName: 'nonExistentMethod', + }); + + expect(matches).toHaveLength(0); + }); + + it('クラスが見つからない場合は空配列を返すこと', () => { + const fileContent = ` + export class OtherClass { + testMethod() { + console.log('test'); + } + } + `; + + const matches = findSymbolInAST(fileContent, '/test/file.ts', { + className: 'NonExistentClass', + memberName: 'testMethod', + }); + + expect(matches).toHaveLength(0); + }); + + it('TypeScript/JavaScript以外のファイルでエラーをスローすること', () => { + expect(() => { + findSymbolInAST('content', '/test/file.py', { + memberName: 'test', + }); + }).toThrow('TypeScript/JavaScript files only'); + }); + + it('JSDocコメントを含む行番号を取得すること', () => { + const fileContent = ` + export class TestClass { + /** + * Multi-line JSDoc + * with description + */ + testMethod() { + console.log('test'); + } + } + `; + + const matches = findSymbolInAST(fileContent, '/test/file.ts', { + className: 'TestClass', + memberName: 'testMethod', + }); + + expect(matches).toHaveLength(1); + // JSDocの開始行が含まれることを確認 + const fileLines = fileContent.split('\n'); + const jsdocStartLine = fileLines.findIndex((line) => line.includes('/**')); + expect(matches[0].startLine).toBeLessThanOrEqual(jsdocStartLine + 1); // 1-indexed + }); + }); + + describe('selectBestSymbolMatch', () => { + const mockMatches: SymbolMatch[] = [ + { + memberName: 'test', + startLine: 10, + endLine: 20, + scopeType: 'function', + confidence: 'high', + }, + { + memberName: 'test', + startLine: 50, + endLine: 60, + scopeType: 'function', + confidence: 'medium', + }, + { + memberName: 'test', + startLine: 100, + endLine: 110, + scopeType: 'function', + confidence: 'low', + }, + ]; + + it('マッチが0個の場合はnullを返すこと', () => { + const result = selectBestSymbolMatch([]); + expect(result).toBeNull(); + }); + + it('マッチが1個の場合はそれを返すこと', () => { + const result = selectBestSymbolMatch([mockMatches[0]]); + expect(result).toBe(mockMatches[0]); + }); + + it('行番号ヒントがある場合、最も近いマッチを返すこと', () => { + const result = selectBestSymbolMatch(mockMatches, { start: 48, end: 62 }); + expect(result).toBe(mockMatches[1]); // 50-60が最も近い + }); + + it('行番号ヒントがない場合、信頼度が最も高いマッチを返すこと', () => { + const result = selectBestSymbolMatch(mockMatches); + expect(result).toBe(mockMatches[0]); // confidence: 'high' + }); + + it('信頼度が同じ場合、最初のマッチを返すこと', () => { + const equalConfidenceMatches: SymbolMatch[] = [ + { ...mockMatches[0], confidence: 'high' }, + { ...mockMatches[1], confidence: 'high' }, + ]; + const result = selectBestSymbolMatch(equalConfidenceMatches); + expect(result).toBe(equalConfidenceMatches[0]); + }); + }); + + describe('ASTキャッシュ', () => { + it('同じファイルを複数回パースしないこと', () => { + const fileContent = ` + export class TestClass { + testMethod() { + console.log('test'); + } + } + `; + + const filePath = '/test/file.ts'; + + // 1回目の呼び出し + const matches1 = findSymbolInAST(fileContent, filePath, { + className: 'TestClass', + memberName: 'testMethod', + }); + + // 2回目の呼び出し(キャッシュが使用される) + const matches2 = findSymbolInAST(fileContent, filePath, { + className: 'TestClass', + memberName: 'testMethod', + }); + + expect(matches1).toEqual(matches2); + }); + + it('clearASTCache()でキャッシュをクリアできること', () => { + const fileContent = ` + export class TestClass { + testMethod() { + console.log('test'); + } + } + `; + + // キャッシュに追加 + findSymbolInAST(fileContent, '/test/file.ts', { + className: 'TestClass', + memberName: 'testMethod', + }); + + // キャッシュをクリア + clearASTCache(); + + // クリア後も正常に動作すること + const matches = findSymbolInAST(fileContent, '/test/file.ts', { + className: 'TestClass', + memberName: 'testMethod', + }); + + expect(matches).toHaveLength(1); + }); + }); +}); diff --git a/test/code-comparison.test.ts b/test/code-comparison.test.ts new file mode 100644 index 0000000..3d2629c --- /dev/null +++ b/test/code-comparison.test.ts @@ -0,0 +1,278 @@ +import * as fs from 'fs'; + +import { jest } from '@jest/globals'; + +import { + compareCodeContent, + dedentCode, + extractLinesFromFile, + searchCodeInFile, +} from '../src/utils/code-comparison'; + +// fsモジュールをモック +jest.mock('fs'); + +const mockedFs = fs as jest.Mocked; + +describe('code-comparison.utils', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('dedentCode', () => { + it('インデントがないコードはそのまま返すこと', () => { + const code = 'function test() {\n return 42;\n}'; + expect(dedentCode(code)).toBe(code); + }); + + it('共通の先頭インデントを除去すること', () => { + const code = ' function test() {\n return 42;\n }'; + const expected = 'function test() {\n return 42;\n}'; + expect(dedentCode(code)).toBe(expected); + }); + + it('空行はそのまま保持すること', () => { + const code = ' function test() {\n\n return 42;\n }'; + const expected = 'function test() {\n\n return 42;\n}'; + expect(dedentCode(code)).toBe(expected); + }); + + it('最小インデントを基準に除去すること', () => { + const code = ' if (true) {\n doSomething();\n }'; + const expected = 'if (true) {\n doSomething();\n}'; + expect(dedentCode(code)).toBe(expected); + }); + + it('空文字列を処理できること', () => { + expect(dedentCode('')).toBe(''); + }); + + it('空行のみの場合はそのまま返すこと', () => { + const code = '\n\n\n'; + expect(dedentCode(code)).toBe(code); + }); + + it('タブインデントも正しく処理すること', () => { + const code = '\t\tfunction test() {\n\t\t\treturn 42;\n\t\t}'; + const expected = 'function test() {\n\treturn 42;\n}'; + expect(dedentCode(code)).toBe(expected); + }); + + it('混合インデント(スペースとタブ)も処理すること', () => { + const code = ' \tfunction test() {\n \t return 42;\n \t}'; + const expected = 'function test() {\n return 42;\n}'; + expect(dedentCode(code)).toBe(expected); + }); + }); + + describe('extractLinesFromFile', () => { + it('指定行範囲のコードを抽出できること', () => { + const fileContent = `line1 +line2 +line3 +line4 +line5`; + + mockedFs.readFileSync.mockReturnValue(fileContent); + + const result = extractLinesFromFile('/test/file.ts', 2, 4); + + expect(result).toBe('line2\nline3\nline4'); + expect(mockedFs.readFileSync).toHaveBeenCalledWith('/test/file.ts', 'utf-8'); + }); + + it('1行のみを抽出できること', () => { + const fileContent = `line1 +line2 +line3`; + + mockedFs.readFileSync.mockReturnValue(fileContent); + + const result = extractLinesFromFile('/test/file.ts', 2, 2); + + expect(result).toBe('line2'); + }); + + it('ファイルの最初から抽出できること', () => { + const fileContent = `line1 +line2 +line3`; + + mockedFs.readFileSync.mockReturnValue(fileContent); + + const result = extractLinesFromFile('/test/file.ts', 1, 2); + + expect(result).toBe('line1\nline2'); + }); + + it('ファイルの最後まで抽出できること', () => { + const fileContent = `line1 +line2 +line3`; + + mockedFs.readFileSync.mockReturnValue(fileContent); + + const result = extractLinesFromFile('/test/file.ts', 2, 3); + + expect(result).toBe('line2\nline3'); + }); + }); + + describe('compareCodeContent', () => { + it('完全に一致するコードをtrueと判定すること', () => { + const code1 = 'const x = 1;'; + const code2 = 'const x = 1;'; + + expect(compareCodeContent(code1, code2)).toBe(true); + }); + + it('空白の違いのみの場合はtrueと判定すること', () => { + const code1 = ' const x = 1; '; + const code2 = 'const x = 1;'; + + expect(compareCodeContent(code1, code2)).toBe(true); + }); + + it('改行の違いを吸収してtrueと判定すること', () => { + const code1 = 'line1\r\nline2'; + const code2 = 'line1\nline2'; + + expect(compareCodeContent(code1, code2)).toBe(true); + }); + + it('インデントの違いを吸収してtrueと判定すること', () => { + const code1 = '\tconst x = 1;'; + const code2 = ' const x = 1;'; + + expect(compareCodeContent(code1, code2)).toBe(true); + }); + + it('コード内容が異なる場合はfalseと判定すること', () => { + const code1 = 'const x = 1;'; + const code2 = 'const y = 2;'; + + expect(compareCodeContent(code1, code2)).toBe(false); + }); + + it('空文字列同士はtrueと判定すること', () => { + expect(compareCodeContent('', '')).toBe(true); + }); + }); + + describe('searchCodeInFile', () => { + it('ファイル内でコードが見つかった場合、その位置を返すこと', () => { + const fileContent = `line1 +const x = 1; +const y = 2; +line4`; + + mockedFs.readFileSync.mockReturnValue(fileContent); + + const result = searchCodeInFile('/test/file.ts', 'const x = 1;\nconst y = 2;'); + + expect(result).toHaveLength(1); + expect(result[0]).toEqual({ start: 2, end: 3 }); + }); + + it('空白の違いを吸収してコードを見つけること', () => { + const fileContent = `line1 + const x = 1; +line3`; + + mockedFs.readFileSync.mockReturnValue(fileContent); + + const result = searchCodeInFile('/test/file.ts', 'const x = 1;'); + + expect(result).toHaveLength(1); + expect(result[0]).toEqual({ start: 2, end: 2 }); + }); + + it('コードが複数箇所で見つかった場合、全ての位置を返すこと', () => { + const fileContent = `const x = 1; +line2 +const x = 1; +line4 +const x = 1;`; + + mockedFs.readFileSync.mockReturnValue(fileContent); + + const result = searchCodeInFile('/test/file.ts', 'const x = 1;'); + + expect(result).toHaveLength(3); + expect(result[0]).toEqual({ start: 1, end: 1 }); + expect(result[1]).toEqual({ start: 3, end: 3 }); + expect(result[2]).toEqual({ start: 5, end: 5 }); + }); + + it('コードが見つからない場合、空配列を返すこと', () => { + const fileContent = `line1 +line2 +line3`; + + mockedFs.readFileSync.mockReturnValue(fileContent); + + const result = searchCodeInFile('/test/file.ts', 'const x = 1;'); + + expect(result).toHaveLength(0); + }); + + it('複数行のコードを検索できること', () => { + const fileContent = `line1 +function test() { + return 1; +} +line5`; + + mockedFs.readFileSync.mockReturnValue(fileContent); + + const result = searchCodeInFile( + '/test/file.ts', + `function test() { + return 1; +}` + ); + + expect(result).toHaveLength(1); + expect(result[0]).toEqual({ start: 2, end: 4 }); + }); + + it('ファイルの先頭でコードを見つけること', () => { + const fileContent = `const x = 1; +line2 +line3`; + + mockedFs.readFileSync.mockReturnValue(fileContent); + + const result = searchCodeInFile('/test/file.ts', 'const x = 1;'); + + expect(result).toHaveLength(1); + expect(result[0]).toEqual({ start: 1, end: 1 }); + }); + + it('ファイルの末尾でコードを見つけること', () => { + const fileContent = `line1 +line2 +const x = 1;`; + + mockedFs.readFileSync.mockReturnValue(fileContent); + + const result = searchCodeInFile('/test/file.ts', 'const x = 1;'); + + expect(result).toHaveLength(1); + expect(result[0]).toEqual({ start: 3, end: 3 }); + }); + + it('部分一致では見つからないこと', () => { + const fileContent = `const x = 1; +const y = 2;`; + + mockedFs.readFileSync.mockReturnValue(fileContent); + + // "const x = 1;"のみを検索("const y = 2;"は含まない) + const result = searchCodeInFile('/test/file.ts', 'const x = 1;'); + + expect(result).toHaveLength(1); + expect(result[0]).toEqual({ start: 1, end: 1 }); + }); + }); +}); diff --git a/test/diff-display.test.ts b/test/diff-display.test.ts new file mode 100644 index 0000000..0ff0b57 --- /dev/null +++ b/test/diff-display.test.ts @@ -0,0 +1,120 @@ +import { displayCodeDiff, displayLineRangeDiff, truncateText } from '../src/utils/diff-display'; + +describe('displayCodeDiff', () => { + it('一致するコードの場合、差分を表示しない', () => { + const code = 'const foo = "bar";\nconst baz = 42;'; + const result = displayCodeDiff(code, code); + + // ヘッダーとフッターが含まれることを確認 + expect(result).toContain('期待されるコード'); + expect(result).toContain('実際のコード'); + // 差分マーカー(- や +)がないことを確認 + expect(result.split('\n').filter((line) => line.startsWith('-')).length).toBe(0); + expect(result.split('\n').filter((line) => line.startsWith('+')).length).toBe(0); + }); + + it('異なるコードの場合、削除と追加を表示する', () => { + const expected = 'const foo = "bar";\nconst baz = 42;'; + const actual = 'const foo = "baz";\nconst qux = 100;'; + const result = displayCodeDiff(expected, actual); + + // 削除された行(期待されるコード) + expect(result).toContain('- const foo = "bar";'); + expect(result).toContain('- const baz = 42;'); + + // 追加された行(実際のコード) + expect(result).toContain('+ const foo = "baz";'); + expect(result).toContain('+ const qux = 100;'); + }); + + it('行数が異なる場合も正しく表示する', () => { + const expected = 'line1\nline2'; + const actual = 'line1\nline2\nline3'; + const result = displayCodeDiff(expected, actual); + + // 一致する行 + expect(result).toContain(' line1'); + expect(result).toContain(' line2'); + + // 追加された行 + expect(result).toContain('+ line3'); + }); + + it('空文字列の比較も処理できる', () => { + const result = displayCodeDiff('', 'new line'); + + expect(result).toContain('+ new line'); + }); +}); + +describe('displayLineRangeDiff', () => { + it('行範囲の差分を正しく表示する', () => { + const code = 'const foo = "bar";\nconst baz = 42;'; + const expectedRange = { start: 10, end: 11 }; + const actualRange = { start: 15, end: 16 }; + const result = displayLineRangeDiff(code, expectedRange, actualRange); + + // ヘッダーが含まれることを確認 + expect(result).toContain('期待される行範囲: 10-11'); + expect(result).toContain('実際の行範囲: 15-16'); + + // 行番号が表示されることを確認(ANSIカラーコードを含む形式) + expect(result).toContain('10'); + expect(result).toContain('15'); + expect(result).toContain('11'); + expect(result).toContain('16'); + + // コードが含まれることを確認 + expect(result).toContain('const foo = "bar";'); + expect(result).toContain('const baz = 42;'); + }); + + it('単一行の場合も正しく表示する', () => { + const code = 'const foo = "bar";'; + const expectedRange = { start: 5, end: 5 }; + const actualRange = { start: 10, end: 10 }; + const result = displayLineRangeDiff(code, expectedRange, actualRange); + + expect(result).toContain('期待される行範囲: 5-5'); + expect(result).toContain('実際の行範囲: 10-10'); + expect(result).toContain('5'); + expect(result).toContain('10'); + expect(result).toContain('const foo = "bar";'); + }); + + it('大きな行番号も正しく表示する', () => { + const code = 'line1\nline2\nline3'; + const expectedRange = { start: 100, end: 102 }; + const actualRange = { start: 200, end: 202 }; + const result = displayLineRangeDiff(code, expectedRange, actualRange); + + expect(result).toContain('100'); + expect(result).toContain('200'); + expect(result).toContain('101'); + expect(result).toContain('201'); + expect(result).toContain('102'); + expect(result).toContain('202'); + expect(result).toContain('line1'); + expect(result).toContain('line2'); + expect(result).toContain('line3'); + }); +}); + +describe('truncateText', () => { + it('最大長以下のテキストはそのまま返す', () => { + const text = 'short text'; + expect(truncateText(text, 20)).toBe(text); + }); + + it('最大長を超えるテキストは切り詰める', () => { + const text = 'This is a very long text that should be truncated'; + const result = truncateText(text, 20); + + expect(result).toBe('This is a very long ...'); + expect(result.length).toBe(23); // 20 + '...' + }); + + it('空文字列を処理できる', () => { + expect(truncateText('', 10)).toBe(''); + }); +}); diff --git a/test/fix.test.ts b/test/fix.test.ts new file mode 100644 index 0000000..f22f2c3 --- /dev/null +++ b/test/fix.test.ts @@ -0,0 +1,863 @@ +/** + * 修正ロジックのテスト + */ + +import * as fs from 'fs'; +import * as readline from 'readline'; + +import { extractLinesFromFile, searchCodeInFileWithScopeExpansion } from '../src/utils/code-comparison'; +import { + applyFix, + createBlockMissingFix, + createContentMismatchFix, + createFixAction, + createLineOutOfRangeFix, + createLocationMismatchFix, + createMultipleSymbolsFoundFix, + createSymbolRangeMismatchFix, + handleMultipleMatches, + isFixableError, +} from '../src/utils/fix'; +import * as markdownEdit from '../src/utils/markdown-edit'; +import * as prompt from '../src/utils/prompt'; +import { CodeRefError, FixAction } from '../src/utils/types'; + +// モック設定 +jest.mock('fs'); +jest.mock('../src/utils/code-comparison', () => ({ + searchCodeInFile: jest.fn(), + searchCodeInFileWithScopeExpansion: jest.fn(), + extractLinesFromFile: jest.fn(), +})); +jest.mock('../src/utils/markdown-edit'); +jest.mock('../src/utils/prompt'); +jest.mock('../src/utils/ast-scope-expansion'); + +const mockFs = fs as jest.Mocked; +const mockExtractLinesFromFile = extractLinesFromFile as jest.MockedFunction< + typeof extractLinesFromFile +>; +const mockSearchCodeInFileWithScopeExpansion = + searchCodeInFileWithScopeExpansion as jest.MockedFunction< + typeof searchCodeInFileWithScopeExpansion + >; +const mockMarkdownEdit = markdownEdit as jest.Mocked; +const mockPrompt = prompt as jest.Mocked; + +describe('isFixableError', () => { + it('修正可能なエラータイプの場合にtrueを返すこと', () => { + const fixableTypes = [ + 'CODE_LOCATION_MISMATCH', + 'CODE_BLOCK_MISSING', + 'CODE_CONTENT_MISMATCH', + 'LINE_OUT_OF_RANGE', + 'SYMBOL_RANGE_MISMATCH', + 'MULTIPLE_SYMBOLS_FOUND', + ]; + + fixableTypes.forEach((type) => { + const error: CodeRefError = { + type: type as any, + message: 'test', + ref: {} as any, + }; + expect(isFixableError(error)).toBe(true); + }); + }); + + it('修正不可能なエラータイプの場合にfalseを返すこと', () => { + const unfixableTypes = ['FILE_NOT_FOUND', 'SYMBOL_NOT_FOUND', 'INVALID_FORMAT']; + + unfixableTypes.forEach((type) => { + const error: CodeRefError = { + type: type as any, + message: 'test', + ref: {} as any, + }; + expect(isFixableError(error)).toBe(false); + }); + }); +}); + +describe('createLocationMismatchFix', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('正しい修正アクションを作成すること', () => { + const error: CodeRefError = { + type: 'CODE_LOCATION_MISMATCH', + message: 'test', + ref: { + fullMatch: '', + refPath: 'test.ts', + startLine: 10, + endLine: 20, + docFile: '/path/to/doc.md', + docLineNumber: 5, + }, + suggestedLines: { start: 15, end: 25 }, + }; + + mockExtractLinesFromFile.mockReturnValue('code content'); + + const result = createLocationMismatchFix(error); + + expect(result).toEqual({ + type: 'UPDATE_LINE_NUMBERS', + error, + description: '行番号を 10-20 から 15-25 に更新', + preview: expect.stringContaining('test.ts:10-20'), + newStartLine: 15, + newEndLine: 25, + newCodeBlock: 'code content', + }); + }); + + it('suggestedLinesがない場合にエラーをスローすること', () => { + const error: CodeRefError = { + type: 'CODE_LOCATION_MISMATCH', + message: 'test', + ref: { + fullMatch: '', + refPath: 'test.ts', + startLine: 10, + endLine: 20, + docFile: '/path/to/doc.md', + docLineNumber: 5, + }, + }; + + expect(() => createLocationMismatchFix(error)).toThrow( + 'CODE_LOCATION_MISMATCHにはsuggestedLinesが必要です' + ); + }); +}); + +describe('createBlockMissingFix', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('正しい修正アクションを作成すること', () => { + const error: CodeRefError = { + type: 'CODE_BLOCK_MISSING', + message: 'test', + ref: { + fullMatch: '', + refPath: 'test.ts', + startLine: 10, + endLine: 20, + docFile: '/path/to/doc.md', + docLineNumber: 5, + }, + }; + + // ドキュメントファイルの内容をモック(コードブロックなし) + mockFs.readFileSync.mockReturnValue( + '# Test Doc\n\n\n\nSome text.' as any + ); + mockExtractLinesFromFile.mockReturnValue('code content'); + + const result = createBlockMissingFix(error); + + expect(result).toEqual({ + type: 'INSERT_CODE_BLOCK', + error, + description: 'test.ts:10-20 からコードブロックを挿入', + preview: expect.stringContaining('code content'), + newCodeBlock: 'code content', + }); + }); + + it('行番号がない場合にエラーをスローすること', () => { + const error: CodeRefError = { + type: 'CODE_BLOCK_MISSING', + message: 'test', + ref: { + fullMatch: '', + refPath: 'test.ts', + startLine: null, + endLine: null, + docFile: '/path/to/doc.md', + docLineNumber: 5, + }, + }; + + expect(() => createBlockMissingFix(error)).toThrow( + 'ファイル全体参照にはコードブロックは不要です' + ); + }); +}); + +describe('createContentMismatchFix', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('AST拡張なしの場合に正しい修正アクションを作成すること', () => { + const error: CodeRefError = { + type: 'CODE_CONTENT_MISMATCH', + message: 'test', + ref: { + fullMatch: '', + refPath: 'test.ts', + startLine: 10, + endLine: 20, + docFile: '/path/to/doc.md', + docLineNumber: 5, + codeBlock: 'old code', + }, + expectedCode: 'expected code', + }; + + mockExtractLinesFromFile.mockReturnValue('actual code'); + mockFs.readFileSync.mockReturnValue('file content' as any); + + // ast-scope-expansionモックで空配列を返す(拡張なし) + const astScopeExpansion = require('../src/utils/ast-scope-expansion'); // eslint-disable-line + astScopeExpansion.expandMatchToScope = jest.fn().mockReturnValue([]); + + const result = createContentMismatchFix(error) as FixAction; + + expect(result.type).toBe('REPLACE_CODE_BLOCK'); + expect(result.newCodeBlock).toBe('actual code'); + }); + + it('AST拡張ありの場合に行番号更新アクションを作成すること', () => { + const error: CodeRefError = { + type: 'CODE_CONTENT_MISMATCH', + message: 'test', + ref: { + fullMatch: '', + refPath: 'test.ts', + startLine: 10, + endLine: 20, + docFile: '/path/to/doc.md', + docLineNumber: 5, + codeBlock: 'old code', + }, + }; + + mockExtractLinesFromFile.mockReturnValue('expanded code'); + mockFs.readFileSync.mockReturnValue('file content' as any); + + // ast-scope-expansionモックで拡張されたマッチを返す + const astScopeExpansion = require('../src/utils/ast-scope-expansion'); // eslint-disable-line + astScopeExpansion.expandMatchToScope = jest.fn().mockReturnValue([ + { + start: 8, + end: 22, + confidence: 'high', + scopeType: 'function', + expansionType: 'ast', + }, + ]); + + const result = createContentMismatchFix(error) as FixAction; + + expect(result.type).toBe('UPDATE_LINE_NUMBERS'); + expect(result.newStartLine).toBe(8); + expect(result.newEndLine).toBe(22); + expect(result.newCodeBlock).toBe('expanded code'); + }); +}); + +describe('createLineOutOfRangeFix', () => { + it('正しい修正アクションを作成すること', () => { + const error: CodeRefError = { + type: 'LINE_OUT_OF_RANGE', + message: '終了行 150 > 100', + ref: { + fullMatch: '', + refPath: 'test.ts', + startLine: 10, + endLine: 150, + docFile: '/path/to/doc.md', + docLineNumber: 5, + }, + }; + + const result = createLineOutOfRangeFix(error); + + expect(result).toEqual({ + type: 'UPDATE_END_LINE', + error, + description: '終了行を 150 から 100 (ファイル末尾) に修正', + preview: expect.stringContaining('10-100'), + newStartLine: 10, + newEndLine: 100, + }); + }); + + it('エラーメッセージから行数を抽出できない場合にエラーをスローすること', () => { + const error: CodeRefError = { + type: 'LINE_OUT_OF_RANGE', + message: 'invalid message', + ref: {} as any, + }; + + expect(() => createLineOutOfRangeFix(error)).toThrow( + 'LINE_OUT_OF_RANGEエラーメッセージから行数を取得できません' + ); + }); +}); + +describe('createSymbolRangeMismatchFix', () => { + it('正しい修正アクションを作成すること', () => { + const error: CodeRefError = { + type: 'SYMBOL_RANGE_MISMATCH', + message: 'test', + ref: { + fullMatch: '', + refPath: 'test.ts', + symbolPath: 'ClassName#methodName', + startLine: 10, + endLine: 20, + docFile: '/path/to/doc.md', + docLineNumber: 5, + }, + suggestedSymbol: { + memberName: 'methodName', + className: 'ClassName', + startLine: 15, + endLine: 25, + scopeType: 'method', + confidence: 'high', + }, + }; + + const result = createSymbolRangeMismatchFix(error); + + expect(result).toEqual({ + type: 'UPDATE_SYMBOL_RANGE', + error, + description: 'シンボル "ClassName#methodName" の行番号を 10-20 から 15-25 に更新', + preview: expect.stringContaining('15-25'), + newStartLine: 15, + newEndLine: 25, + }); + }); + + it('suggestedSymbolがない場合にエラーをスローすること', () => { + const error: CodeRefError = { + type: 'SYMBOL_RANGE_MISMATCH', + message: 'test', + ref: {} as any, + }; + + expect(() => createSymbolRangeMismatchFix(error)).toThrow( + 'SYMBOL_RANGE_MISMATCHにはsuggestedSymbolが必要です' + ); + }); +}); + +describe('createMultipleSymbolsFoundFix', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('ユーザーが選択したシンボルで修正アクションを作成すること', async () => { + const error: CodeRefError = { + type: 'MULTIPLE_SYMBOLS_FOUND', + message: 'test', + ref: { + fullMatch: '', + refPath: 'test.ts', + symbolPath: 'methodName', + startLine: null, + endLine: null, + docFile: '/path/to/doc.md', + docLineNumber: 5, + }, + foundSymbols: [ + { + memberName: 'methodName', + startLine: 10, + endLine: 20, + scopeType: 'method', + confidence: 'high', + }, + { + memberName: 'methodName', + startLine: 50, + endLine: 60, + scopeType: 'method', + confidence: 'high', + }, + ], + }; + + const mockRl = {} as readline.Interface; + mockPrompt.askSelectOption.mockResolvedValue(1); + + const result = await createMultipleSymbolsFoundFix(error, mockRl); + + expect(result).toEqual({ + type: 'UPDATE_SYMBOL_RANGE', + error, + description: 'シンボル "methodName" の行番号を追加: 50-60', + preview: expect.stringContaining('50-60'), + newStartLine: 50, + newEndLine: 60, + }); + }); + + it('foundSymbolsがない場合にエラーをスローすること', async () => { + const error: CodeRefError = { + type: 'MULTIPLE_SYMBOLS_FOUND', + message: 'test', + ref: {} as any, + }; + + const mockRl = {} as readline.Interface; + + await expect(createMultipleSymbolsFoundFix(error, mockRl)).rejects.toThrow( + 'MULTIPLE_SYMBOLS_FOUNDにはfoundSymbolsが必要です' + ); + }); +}); + +describe('createFixAction', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('修正不可能なエラーの場合にnullを返すこと', async () => { + const error: CodeRefError = { + type: 'FILE_NOT_FOUND', + message: 'test', + ref: {} as any, + }; + + const result = await createFixAction(error); + + expect(result).toBeNull(); + }); + + it('CODE_LOCATION_MISMATCHの場合に適切な修正アクションを返すこと', async () => { + const error: CodeRefError = { + type: 'CODE_LOCATION_MISMATCH', + message: 'test', + ref: { + fullMatch: '', + refPath: 'test.ts', + startLine: 10, + endLine: 20, + docFile: '/path/to/doc.md', + docLineNumber: 5, + }, + suggestedLines: { start: 15, end: 25 }, + }; + + mockExtractLinesFromFile.mockReturnValue('code content'); + + const result = (await createFixAction(error)) as FixAction; + + expect(result?.type).toBe('UPDATE_LINE_NUMBERS'); + }); + + it('MULTIPLE_SYMBOLS_FOUNDでreadline.Interfaceがない場合にエラーをスローすること', async () => { + const error: CodeRefError = { + type: 'MULTIPLE_SYMBOLS_FOUND', + message: 'test', + ref: {} as any, + foundSymbols: [ + { + memberName: 'test', + startLine: 1, + endLine: 10, + scopeType: 'method', + confidence: 'high', + }, + ], + }; + + await expect(createFixAction(error)).rejects.toThrow( + 'MULTIPLE_SYMBOLS_FOUNDにはreadline.Interfaceが必要です' + ); + }); +}); + +describe('優先順位付けロジック', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('handleMultipleMatches()', () => { + const createMockError = ( + startLine: number, + endLine: number, + codeBlock: string = 'test code' + ): CodeRefError => ({ + type: 'CODE_LOCATION_MISMATCH', + message: 'Test error', + ref: { + fullMatch: '', + refPath: 'test.ts', + startLine, + endLine, + docFile: 'test.md', + codeBlock, + }, + }); + + const createMockReadline = (answer: number = 0): readline.Interface => { + const rl = { + question: jest.fn((_query, callback) => { + // ユーザーが選択肢を選んだとして、すぐにコールバックを呼ぶ + callback(`${answer + 1}`); // 1-indexed の選択肢 + }), + close: jest.fn(), + } as unknown as readline.Interface; + return rl; + }; + + it('単一マッチの場合、そのマッチを返す', async () => { + const error = createMockError(10, 20); + const rl = createMockReadline(); + + mockSearchCodeInFileWithScopeExpansion.mockReturnValue([ + { start: 15, end: 25, confidence: 'high', expansionType: 'ast', scopeType: 'function' }, + ]); + + const result = await handleMultipleMatches(error, rl); + + expect(result).not.toBeNull(); + expect(result?.newStartLine).toBe(15); + expect(result?.newEndLine).toBe(25); + }); + + it('複数マッチで高信頼度が1つの場合、自動選択する', async () => { + const error = createMockError(10, 20); + const rl = createMockReadline(); + + // 1つだけ高信頼度のマッチがある場合、自動選択される + mockSearchCodeInFileWithScopeExpansion.mockReturnValue([ + { + start: 10, + end: 20, + confidence: 'high', + expansionType: 'ast', + scopeType: 'interface', + }, + { start: 50, end: 60, confidence: 'low', expansionType: 'none', scopeType: 'unknown' }, + { start: 100, end: 110, confidence: 'low', expansionType: 'none', scopeType: 'unknown' }, + ]); + + const result = await handleMultipleMatches(error, rl); + + expect(result).not.toBeNull(); + expect(result?.newStartLine).toBe(10); + expect(result?.newEndLine).toBe(20); + // 自動選択されたので、ユーザーに質問しない + expect(rl.question).not.toHaveBeenCalled(); + }); + + it('元の位置に近いマッチを優先する', async () => { + const error = createMockError(10, 20); + const rl = createMockReadline(); + + // 距離の異なる3つのマッチ(全て同じ信頼度) + mockSearchCodeInFileWithScopeExpansion.mockReturnValue([ + { start: 100, end: 110, confidence: 'medium', expansionType: 'none', scopeType: 'unknown' }, // 90行離れている + { start: 12, end: 22, confidence: 'medium', expansionType: 'none', scopeType: 'unknown' }, // 2行ずれ - 最も近い + { start: 50, end: 60, confidence: 'medium', expansionType: 'none', scopeType: 'unknown' }, // 40行離れている + ]); + + const result = await handleMultipleMatches(error, rl); + + expect(result).not.toBeNull(); + // ソート後の配列から選択される(モックのreadlineが選択) + expect(result?.newStartLine).toBe(50); + expect(result?.newEndLine).toBe(60); + }); + + it('スコープタイプで優先順位付けする', async () => { + const error = createMockError(10, 20); + const rl = createMockReadline(); + + // 同じ距離だが、スコープタイプが異なる(複数のhighがあるのでユーザーに選択させる) + mockSearchCodeInFileWithScopeExpansion.mockReturnValue([ + { start: 12, end: 22, confidence: 'high', expansionType: 'ast', scopeType: 'function' }, + { start: 13, end: 23, confidence: 'high', expansionType: 'ast', scopeType: 'interface' }, + { start: 14, end: 24, confidence: 'high', expansionType: 'ast', scopeType: 'const' }, + ]); + + const result = await handleMultipleMatches(error, rl); + + expect(result).not.toBeNull(); + // 複数のhigh信頼度マッチがあり、その中でスコープタイプの優先度が高いマッチが自動選択される + expect(result?.newStartLine).toBe(13); + expect(result?.newEndLine).toBe(23); + // 高信頼度マッチが1つだけ(ソート後の最優先)なので自動選択される + expect(rl.question).not.toHaveBeenCalled(); + }); + + it('信頼度が最優先される', async () => { + const error = createMockError(10, 20); + const rl = createMockReadline(); + + // 距離は遠いが信頼度が高いマッチがある + mockSearchCodeInFileWithScopeExpansion.mockReturnValue([ + { start: 11, end: 21, confidence: 'low', expansionType: 'none', scopeType: 'unknown' }, // 近いが低信頼度 + { start: 100, end: 110, confidence: 'high', expansionType: 'ast', scopeType: 'interface' }, // 遠いが高信頼度 + ]); + + const result = await handleMultipleMatches(error, rl); + + expect(result).not.toBeNull(); + // 高信頼度が1つだけなので自動選択される + expect(result?.newStartLine).toBe(100); + expect(result?.newEndLine).toBe(110); + }); + + it('マッチが見つからない場合、nullを返す', async () => { + const error = createMockError(10, 20); + const rl = createMockReadline(); + + mockSearchCodeInFileWithScopeExpansion.mockReturnValue([]); + + const result = await handleMultipleMatches(error, rl); + + expect(result).toBeNull(); + }); + + it('codeBlockがない場合、nullを返す', async () => { + const error: CodeRefError = { + type: 'CODE_LOCATION_MISMATCH', + message: 'Test error', + ref: { + fullMatch: '', + refPath: 'test.ts', + startLine: 10, + endLine: 20, + docFile: 'test.md', + // codeBlock なし + }, + }; + const rl = createMockReadline(); + + const result = await handleMultipleMatches(error, rl); + + expect(result).toBeNull(); + expect(mockSearchCodeInFileWithScopeExpansion).not.toHaveBeenCalled(); + }); + }); +}); + +describe('行番号管理', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('applyFix() のライン delta', () => { + it('INSERT_CODE_BLOCK で行が追加される場合、正のdeltaを返す', () => { + const originalContent = `# Test Document + + + +Some content`; + + const newContent = `# Test Document + + +\`\`\`typescript +const test = 'hello'; +console.log(test); +\`\`\` + +Some content`; + + // モック設定 + mockFs.readFileSync.mockReturnValue(originalContent as any); + mockFs.writeFileSync.mockImplementation(() => {}); + mockMarkdownEdit.insertCodeBlockAfterComment.mockReturnValue(newContent); + + const action: FixAction = { + type: 'INSERT_CODE_BLOCK', + error: { + type: 'CODE_BLOCK_MISSING', + message: 'Test', + ref: { + fullMatch: '', + refPath: 'test.ts', + startLine: 1, + endLine: 5, + docFile: 'test.md', + }, + }, + description: 'Insert code block', + preview: 'Insert code block after CODE_REF comment', + newStartLine: 1, + newEndLine: 5, + newCodeBlock: "const test = 'hello';\nconsole.log(test);", + }; + + const lineDelta = applyFix(action); + + expect(mockMarkdownEdit.insertCodeBlockAfterComment).toHaveBeenCalled(); + expect(mockFs.writeFileSync).toHaveBeenCalled(); + // 元の5行 → 新しい9行 = +4行 + expect(lineDelta).toBe(4); + }); + + it('REPLACE_CODE_BLOCK で行が増加する場合、正のdeltaを返す', () => { + const originalContent = `# Test Document + + +\`\`\`typescript +const test = 'hello'; +\`\`\` + +Some content`; + + const newContent = `# Test Document + + +\`\`\`typescript +const test = 'hello'; +console.log(test); +console.log('more'); +\`\`\` + +Some content`; + + // モック設定 + mockFs.readFileSync.mockReturnValue(originalContent as any); + mockFs.writeFileSync.mockImplementation(() => {}); + mockMarkdownEdit.replaceCodeBlock.mockReturnValue(newContent); + + const action: FixAction = { + type: 'REPLACE_CODE_BLOCK', + error: { + type: 'CODE_CONTENT_MISMATCH', + message: 'Test', + ref: { + fullMatch: '', + refPath: 'test.ts', + startLine: 1, + endLine: 5, + docFile: 'test.md', + docLineNumber: 3, + codeBlock: "const test = 'hello';", + }, + }, + description: 'Replace code block', + preview: 'Replace existing code block', + newStartLine: 1, + newEndLine: 5, + newCodeBlock: "const test = 'hello';\nconsole.log(test);\nconsole.log('more');", + }; + + const lineDelta = applyFix(action); + + expect(mockMarkdownEdit.replaceCodeBlock).toHaveBeenCalled(); + expect(mockFs.writeFileSync).toHaveBeenCalled(); + // 元の8行 → 新しい10行 = +2行 + expect(lineDelta).toBe(2); + }); + + it('UPDATE_LINE_NUMBERS で行数変化がない場合、0を返す', () => { + const originalContent = `# Test Document + + + +Some content`; + + const newContent = `# Test Document + + + +Some content`; + + // モック設定 + mockFs.readFileSync.mockReturnValue(originalContent as any); + mockFs.writeFileSync.mockImplementation(() => {}); + mockMarkdownEdit.replaceCodeRefComment.mockReturnValue(newContent); + mockMarkdownEdit.findCodeBlockPosition.mockReturnValue(null); + + const action: FixAction = { + type: 'UPDATE_LINE_NUMBERS', + error: { + type: 'CODE_LOCATION_MISMATCH', + message: 'Test', + ref: { + fullMatch: '', + refPath: 'test.ts', + startLine: 1, + endLine: 5, + docFile: 'test.md', + }, + }, + description: 'Update line numbers', + preview: 'Update CODE_REF line numbers', + newStartLine: 10, + newEndLine: 15, + }; + + const lineDelta = applyFix(action); + + expect(mockMarkdownEdit.replaceCodeRefComment).toHaveBeenCalled(); + expect(mockFs.writeFileSync).toHaveBeenCalled(); + // 行数変化なし(コメントのみ更新) + expect(lineDelta).toBe(0); + }); + + it('REPLACE_CODE_BLOCK で行が減少する場合、負のdeltaを返す', () => { + const originalContent = `# Test Document + + +\`\`\`typescript +const test = 'hello'; +console.log(test); +console.log('more'); +\`\`\` + +Some content`; + + const newContent = `# Test Document + + +\`\`\`typescript +const test = 'hello'; +\`\`\` + +Some content`; + + // モック設定 + mockFs.readFileSync.mockReturnValue(originalContent as any); + mockFs.writeFileSync.mockImplementation(() => {}); + mockMarkdownEdit.replaceCodeBlock.mockReturnValue(newContent); + + const action: FixAction = { + type: 'REPLACE_CODE_BLOCK', + error: { + type: 'CODE_CONTENT_MISMATCH', + message: 'Test', + ref: { + fullMatch: '', + refPath: 'test.ts', + startLine: 1, + endLine: 5, + docFile: 'test.md', + docLineNumber: 3, + codeBlock: "const test = 'hello';\nconsole.log(test);\nconsole.log('more');", + }, + }, + description: 'Replace code block', + preview: 'Replace existing code block', + newStartLine: 1, + newEndLine: 5, + newCodeBlock: "const test = 'hello';", + }; + + const lineDelta = applyFix(action); + + expect(mockMarkdownEdit.replaceCodeBlock).toHaveBeenCalled(); + expect(mockFs.writeFileSync).toHaveBeenCalled(); + // 元の10行 → 新しい8行 = -2行 + expect(lineDelta).toBe(-2); + }); + }); +}); diff --git a/test/ignore-pattern.test.ts b/test/ignore-pattern.test.ts new file mode 100644 index 0000000..e1a2817 --- /dev/null +++ b/test/ignore-pattern.test.ts @@ -0,0 +1,232 @@ +import * as fs from 'fs'; + +import { isIgnored, loadDocsignorePatterns, matchesPattern } from '../src/utils/ignore-pattern'; + +// fsをモック +jest.mock('fs'); +const mockedFs = fs as jest.Mocked; + +describe('ignore-pattern.utils', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('matchesPattern', () => { + describe('ディレクトリパターン(末尾が/)', () => { + it('ディレクトリ配下のファイルにマッチする', () => { + expect(matchesPattern('node_modules/package.json', 'node_modules/')).toBe(true); + expect(matchesPattern('node_modules/nested/file.js', 'node_modules/')).toBe(true); + }); + + it('末尾のスラッシュなしでもマッチする', () => { + expect(matchesPattern('node_modules/package.json', 'node_modules/')).toBe(true); + }); + + it('異なるディレクトリにはマッチしない', () => { + expect(matchesPattern('src/index.ts', 'node_modules/')).toBe(false); + }); + }); + + describe('**/パターン(任意のディレクトリ階層)', () => { + it('任意の階層の指定ファイルにマッチする', () => { + expect(matchesPattern('src/test.log', '**/*.log')).toBe(true); + expect(matchesPattern('nested/deep/test.log', '**/*.log')).toBe(true); + }); + + it('ファイル名が含まれる場合にマッチする', () => { + expect(matchesPattern('logs/debug-file.json', '**/debug-*.json')).toBe(true); + expect(matchesPattern('src/logs/debug-2024.json', '**/debug-*.json')).toBe(true); + }); + + it('パス内のディレクトリ名にマッチする', () => { + expect(matchesPattern('node_modules/package/index.js', 'node_modules/')).toBe(true); + expect(matchesPattern('src/node_modules/index.js', 'node_modules/')).toBe(false); + }); + + it('サフィックスマッチング', () => { + expect(matchesPattern('path/to/config.json', '**/config.json')).toBe(true); + expect(matchesPattern('config.json', '**/config.json')).toBe(true); + }); + + it('マッチしないファイル', () => { + expect(matchesPattern('src/index.ts', '**/*.log')).toBe(false); + }); + }); + + describe('*ワイルドカード', () => { + it('ワイルドカードパターンにマッチする', () => { + expect(matchesPattern('test.config.js', '*.config.js')).toBe(true); + expect(matchesPattern('eslint.config.js', '*.config.js')).toBe(true); + }); + + it('拡張子のみのワイルドカード', () => { + expect(matchesPattern('file.log', '*.log')).toBe(true); + expect(matchesPattern('debug.log', '*.log')).toBe(true); + }); + + it('マッチしないファイル', () => { + expect(matchesPattern('test.js', '*.config.js')).toBe(false); + }); + }); + + describe('完全一致またはプレフィックスマッチ', () => { + it('完全一致する', () => { + expect(matchesPattern('README.md', 'README.md')).toBe(true); + }); + + it('プレフィックスマッチする', () => { + expect(matchesPattern('src/index.ts', 'src')).toBe(true); + expect(matchesPattern('src/nested/file.ts', 'src')).toBe(true); + }); + + it('マッチしないパス', () => { + expect(matchesPattern('source/index.ts', 'src')).toBe(false); + expect(matchesPattern('README.txt', 'README.md')).toBe(false); + }); + }); + + describe('エッジケース', () => { + it('ドットで始まるファイル', () => { + expect(matchesPattern('.env', '.env')).toBe(true); + expect(matchesPattern('.env.local', '.env*')).toBe(true); + }); + + it('複数のワイルドカード', () => { + expect(matchesPattern('test.spec.ts', '*.spec.*')).toBe(true); + }); + + it('疑問符ワイルドカード', () => { + expect(matchesPattern('file1.ts', 'file?.ts')).toBe(true); + expect(matchesPattern('file12.ts', 'file?.ts')).toBe(false); + }); + }); + }); + + describe('isIgnored', () => { + it('いずれかのパターンにマッチする場合はtrueを返す', () => { + const patterns = ['node_modules/', '*.log', 'dist/']; + + expect(isIgnored('node_modules/package.json', patterns)).toBe(true); + expect(isIgnored('debug.log', patterns)).toBe(true); + expect(isIgnored('dist/bundle.js', patterns)).toBe(true); + }); + + it('どのパターンにもマッチしない場合はfalseを返す', () => { + const patterns = ['node_modules/', '*.log', 'dist/']; + + expect(isIgnored('src/index.ts', patterns)).toBe(false); + expect(isIgnored('README.md', patterns)).toBe(false); + }); + + it('空のパターン配列の場合はfalseを返す', () => { + expect(isIgnored('any/file.ts', [])).toBe(false); + }); + + it('複数パターンの組み合わせ', () => { + const patterns = ['**/*.test.ts', 'coverage/', '.env*']; + + expect(isIgnored('src/utils/helper.test.ts', patterns)).toBe(true); + expect(isIgnored('coverage/lcov.info', patterns)).toBe(true); + expect(isIgnored('.env.local', patterns)).toBe(true); + expect(isIgnored('src/index.ts', patterns)).toBe(false); + }); + }); + + describe('loadDocsignorePatterns', () => { + it('.docsignoreファイルが存在する場合はパターンを読み込む', () => { + const mockContent = `# Comment +node_modules/ +*.log + +# Another comment +dist/ +coverage/`; + + mockedFs.existsSync.mockReturnValue(true); + mockedFs.readFileSync.mockReturnValue(mockContent); + + const result = loadDocsignorePatterns('/path/to/.docsignore'); + + expect(result).toEqual(['node_modules/', '*.log', 'dist/', 'coverage/']); + expect(mockedFs.existsSync).toHaveBeenCalledWith('/path/to/.docsignore'); + expect(mockedFs.readFileSync).toHaveBeenCalledWith('/path/to/.docsignore', 'utf-8'); + }); + + it('空行を除外する', () => { + const mockContent = `node_modules/ + + +*.log + +`; + + mockedFs.existsSync.mockReturnValue(true); + mockedFs.readFileSync.mockReturnValue(mockContent); + + const result = loadDocsignorePatterns('/path/to/.docsignore'); + + expect(result).toEqual(['node_modules/', '*.log']); + }); + + it('コメント行を除外する', () => { + const mockContent = `# This is a comment +node_modules/ +# Another comment +*.log`; + + mockedFs.existsSync.mockReturnValue(true); + mockedFs.readFileSync.mockReturnValue(mockContent); + + const result = loadDocsignorePatterns('/path/to/.docsignore'); + + expect(result).toEqual(['node_modules/', '*.log']); + }); + + it('前後の空白をトリムする', () => { + const mockContent = ` node_modules/ + *.log +`; + + mockedFs.existsSync.mockReturnValue(true); + mockedFs.readFileSync.mockReturnValue(mockContent); + + const result = loadDocsignorePatterns('/path/to/.docsignore'); + + expect(result).toEqual(['node_modules/', '*.log']); + }); + + it('.docsignoreファイルが存在しない場合は空配列を返す', () => { + mockedFs.existsSync.mockReturnValue(false); + + const result = loadDocsignorePatterns('/path/to/.docsignore'); + + expect(result).toEqual([]); + expect(mockedFs.existsSync).toHaveBeenCalledWith('/path/to/.docsignore'); + expect(mockedFs.readFileSync).not.toHaveBeenCalled(); + }); + + it('空のファイルの場合は空配列を返す', () => { + mockedFs.existsSync.mockReturnValue(true); + mockedFs.readFileSync.mockReturnValue(''); + + const result = loadDocsignorePatterns('/path/to/.docsignore'); + + expect(result).toEqual([]); + }); + + it('コメントと空行のみの場合は空配列を返す', () => { + const mockContent = `# Comment only + +# Another comment + +`; + + mockedFs.existsSync.mockReturnValue(true); + mockedFs.readFileSync.mockReturnValue(mockContent); + + const result = loadDocsignorePatterns('/path/to/.docsignore'); + + expect(result).toEqual([]); + }); + }); +}); diff --git a/test/markdown.test.ts b/test/markdown.test.ts new file mode 100644 index 0000000..da7681b --- /dev/null +++ b/test/markdown.test.ts @@ -0,0 +1,373 @@ +import { describe, expect, it } from '@jest/globals'; + +import { + associateCodeBlocksWithRefs, + extractCodeBlockAfterComment, + normalizeCode, +} from '../src/utils/markdown'; +import type { CodeRef } from '../src/utils/types'; + +describe('markdown.utils', () => { + describe('extractCodeBlockAfterComment', () => { + it('CODE_REFの直後にコードブロックがある場合、正しく抽出できること', () => { + const content = ` + +\`\`\`typescript +export function example() { + return 'test'; +} +\`\`\``; + + const commentIndex = content.indexOf(' + +これは説明文です。 + +\`\`\`typescript +const x = 1; +\`\`\``; + + const commentIndex = content.indexOf(' + +これは説明文だけです。`; + + const commentIndex = content.indexOf(' + +\`\`\` +const y = 2; +\`\`\``; + + const commentIndex = content.indexOf(' + +\`\`\`javascript +const z = 3; +\`\`\``; + + const commentIndex = content.indexOf(' + +\`\`\`typescript +\`\`\``; + + const commentIndex = content.indexOf('${filler}\`\`\`typescript +const x = 1; +\`\`\``; + + const commentIndex = content.indexOf(' + +\`\`\`typescript +const a = 1; +\`\`\` + + + +\`\`\`typescript +const b = 2; +\`\`\``; + + const refs: CodeRef[] = [ + { + fullMatch: '', + refPath: 'src/a.ts', + startLine: 1, + endLine: 5, + docFile: 'test.md', + docLineNumber: 3, + }, + { + fullMatch: '', + refPath: 'src/b.ts', + startLine: 10, + endLine: 15, + docFile: 'test.md', + docLineNumber: 7, + }, + ]; + + const result = associateCodeBlocksWithRefs(content, refs); + + expect(result).toHaveLength(2); + expect(result[0].codeBlock).toBe('const a = 1;\n'); + expect(result[0].docLineNumber).toBe(3); + expect(result[0].codeBlockStartOffset).toBe( + content.indexOf('') + ); + expect(result[1].codeBlock).toBe('const b = 2;\n'); + expect(result[1].docLineNumber).toBe(7); + expect(result[1].codeBlockStartOffset).toBe( + content.indexOf('') + ); + }); + + it('コードブロックがない場合、codeBlockプロパティが追加されないこと', () => { + const content = ` + +これは説明文だけです。`; + + const refs: CodeRef[] = [ + { + fullMatch: '', + refPath: 'src/a.ts', + startLine: 1, + endLine: 5, + docFile: 'test.md', + }, + ]; + + const result = associateCodeBlocksWithRefs(content, refs); + + expect(result).toHaveLength(1); + expect(result[0].codeBlock).toBeUndefined(); + expect(result[0].codeBlockStartOffset).toBeUndefined(); + }); + + it('fullMatchが見つからない場合、元のrefをそのまま返すこと', () => { + const content = ` + +\`\`\`typescript +const other = 1; +\`\`\``; + + const refs: CodeRef[] = [ + { + fullMatch: '', // contentに存在しない + refPath: 'src/a.ts', + startLine: 1, + endLine: 5, + docFile: 'test.md', + }, + ]; + + const result = associateCodeBlocksWithRefs(content, refs); + + expect(result).toHaveLength(1); + expect(result[0]).toEqual(refs[0]); // 変更されていない + }); + + it('CODE_REFとコードブロックの間に見出しがある場合、codeBlockプロパティが追加されないこと', () => { + const content = ` + +#### これは見出しです + +\`\`\`typescript +const a = 1; +\`\`\``; + + const refs: CodeRef[] = [ + { + fullMatch: '', + refPath: 'src/a.ts', + startLine: 1, + endLine: 5, + docFile: 'test.md', + }, + ]; + + const result = associateCodeBlocksWithRefs(content, refs); + + expect(result).toHaveLength(1); + expect(result[0].codeBlock).toBeUndefined(); + expect(result[0].codeBlockStartOffset).toBeUndefined(); + }); + + it('CODE_REFとコードブロックの間に段落がある場合、codeBlockプロパティが追加されないこと', () => { + const content = ` + +これは説明文です。 + +\`\`\`typescript +const a = 1; +\`\`\``; + + const refs: CodeRef[] = [ + { + fullMatch: '', + refPath: 'src/a.ts', + startLine: 1, + endLine: 5, + docFile: 'test.md', + }, + ]; + + const result = associateCodeBlocksWithRefs(content, refs); + + expect(result).toHaveLength(1); + expect(result[0].codeBlock).toBeUndefined(); + expect(result[0].codeBlockStartOffset).toBeUndefined(); + }); + + it('CODE_REFの直後(空行のみ)にコードブロックがある場合、codeBlockプロパティが追加されること', () => { + const content = ` + +\`\`\`typescript +const a = 1; +\`\`\``; + + const refs: CodeRef[] = [ + { + fullMatch: '', + refPath: 'src/a.ts', + startLine: 1, + endLine: 5, + docFile: 'test.md', + }, + ]; + + const result = associateCodeBlocksWithRefs(content, refs); + + expect(result).toHaveLength(1); + expect(result[0].codeBlock).toBe('const a = 1;\n'); + }); + + it('CODE_REFの直後(空行なし)にコードブロックがある場合、codeBlockプロパティが追加されること', () => { + const content = ` +\`\`\`typescript +const a = 1; +\`\`\``; + + const refs: CodeRef[] = [ + { + fullMatch: '', + refPath: 'src/a.ts', + startLine: 1, + endLine: 5, + docFile: 'test.md', + }, + ]; + + const result = associateCodeBlocksWithRefs(content, refs); + + expect(result).toHaveLength(1); + expect(result[0].codeBlock).toBe('const a = 1;\n'); + }); + }); + + describe('normalizeCode', () => { + it('空白のみが異なるコードを正規化できること', () => { + const code1 = ' const x = 1; '; + const code2 = 'const x = 1;'; + + expect(normalizeCode(code1)).toBe(normalizeCode(code2)); + expect(normalizeCode(code1)).toBe('constx=1;'); + }); + + it('Windows改行とUnix改行を統一すること', () => { + const codeWindows = 'line1\r\nline2\r\nline3'; + const codeUnix = 'line1\nline2\nline3'; + + expect(normalizeCode(codeWindows)).toBe(normalizeCode(codeUnix)); + expect(normalizeCode(codeUnix)).toBe('line1line2line3'); + }); + + it('改行とインデントをすべて削除すること', () => { + const code = ` line1 + line2 + line3 `; + + const result = normalizeCode(code); + expect(result).toBe('line1line2line3'); + }); + + it('連続する空白をすべて削除すること', () => { + const code = 'const x = 1;'; + const expected = 'constx=1;'; + + expect(normalizeCode(code)).toBe(expected); + }); + + it('すべての空白を削除すること', () => { + const code = '\n\nconst x = 1;\n\n'; + const expected = 'constx=1;'; + + expect(normalizeCode(code)).toBe(expected); + }); + + it('空行もすべて削除すること', () => { + const code = `line1 + +line3`; + const result = normalizeCode(code); + + expect(result).toBe('line1line3'); + }); + + it('タブとスペースの違いを吸収すること', () => { + const codeWithTabs = '\tconst\tx\t=\t1;'; + const codeWithSpaces = ' const x = 1;'; + + expect(normalizeCode(codeWithTabs)).toBe(normalizeCode(codeWithSpaces)); + expect(normalizeCode(codeWithTabs)).toBe('constx=1;'); + }); + + it('空文字列の場合、空文字列を返すこと', () => { + expect(normalizeCode('')).toBe(''); + }); + + it('空行のみの場合、空文字列を返すこと', () => { + expect(normalizeCode('\n\n\n')).toBe(''); + }); + + it('括弧の内側の改行も削除すること', () => { + const code1 = 'func(\n arg1,\n arg2\n)'; + const code2 = 'func(arg1,arg2)'; + + expect(normalizeCode(code1)).toBe(normalizeCode(code2)); + expect(normalizeCode(code1)).toBe('func(arg1,arg2)'); + }); + }); +}); diff --git a/test/validate.test.ts b/test/validate.test.ts new file mode 100644 index 0000000..7accf1c --- /dev/null +++ b/test/validate.test.ts @@ -0,0 +1,804 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +import { jest } from '@jest/globals'; + +import type { CodeRef } from '../src/utils/types'; +import { extractCodeRefs, findMarkdownFiles, validateCodeRef } from '../src/core/validate'; + +// テスト用のモックプロジェクトルート +const mockProjectRoot = '/project'; + +// モックの設定 +jest.mock('fs'); +jest.mock('path', () => { + const actualPath = jest.requireActual('path'); + return { + ...actualPath, + sep: '/', + join: jest.fn((...args: string[]) => args.join('/')), + resolve: jest.fn((...args: string[]) => { + // 引数がない場合は現在のディレクトリ + if (args.length === 0) { + return mockProjectRoot; + } + // 1つ目の引数が絶対パスの場合 + if (args[0] && args[0].startsWith('/')) { + // 絶対パスから相対パスを解決 + return args.reduce((acc: string, arg: string) => { + if (arg === '..') { + // 親ディレクトリに移動 + const parts = acc.split('/').filter(Boolean); + parts.pop(); + return '/' + parts.join('/'); + } else if (arg === '.') { + // 現在のディレクトリ + return acc; + } else if (arg && !arg.startsWith('/')) { + // 相対パスを追加 + return `${acc}/${arg}`; + } + return arg || acc; + }, ''); + } + // デフォルト: 全ての引数を結合 + return '/' + args.filter((arg) => arg && arg !== '.').join('/'); + }), + relative: jest.fn((from: string, to: string) => to.replace(from + '/', '')), + }; +}); + +const mockedFs = fs as jest.Mocked; +const mockedPath = path as jest.Mocked; + +describe('validate-docs-code', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('findMarkdownFiles', () => { + it('マークダウンファイルのみを返すこと', () => { + // モックデータの設定 + const mockDirents = [ + { name: 'file1.md', isDirectory: () => false, isFile: () => true }, + { name: 'file2.txt', isDirectory: () => false, isFile: () => true }, + { name: 'subdir', isDirectory: () => true, isFile: () => false }, + ]; + + const mockSubdirents = [{ name: 'file3.md', isDirectory: () => false, isFile: () => true }]; + + mockedFs.readdirSync + .mockReturnValueOnce(mockDirents as any) + .mockReturnValueOnce(mockSubdirents as any); + + const result = findMarkdownFiles('/test'); + + expect(result).toEqual(['/test/file1.md', '/test/subdir/file3.md']); + expect(mockedFs.readdirSync).toHaveBeenCalledWith('/test', { withFileTypes: true }); + expect(mockedFs.readdirSync).toHaveBeenCalledWith('/test/subdir', { withFileTypes: true }); + }); + + it('空のディレクトリでは空の配列を返すこと', () => { + mockedFs.readdirSync.mockReturnValue([]); + + const result = findMarkdownFiles('/empty'); + + expect(result).toEqual([]); + }); + }); + + describe('extractCodeRefs', () => { + it('CODE_REFコメントを正しく抽出すること', () => { + const content = ` +# テストドキュメント + + +コードの例: + + +特定の行範囲: + + +他のファイル: + `; + + const result = extractCodeRefs(content, '/docs/test.md'); + + expect(result).toHaveLength(3); + + expect(result[0]).toMatchObject({ + fullMatch: '', + refPath: 'src/example.ts', + startLine: null, + endLine: null, + docFile: '/docs/test.md', + docLineNumber: 4, + }); + + expect(result[1]).toMatchObject({ + fullMatch: '', + refPath: 'src/example.ts', + startLine: 10, + endLine: 20, + docFile: '/docs/test.md', + docLineNumber: 7, + }); + + expect(result[2]).toMatchObject({ + fullMatch: '', + refPath: 'src/other.js', + startLine: 5, + endLine: 15, + docFile: '/docs/test.md', + docLineNumber: 10, + }); + }); + + it('CODE_REFがない場合は空の配列を返すこと', () => { + const content = ` +# テストドキュメント + +これは通常のマークダウンファイルです。 + `; + + const result = extractCodeRefs(content, '/docs/test.md'); + + expect(result).toEqual([]); + }); + + it('スペースが含まれるCODE_REFを正しく処理すること', () => { + const content = ``; + + const result = extractCodeRefs(content, '/docs/test.md'); + + expect(result).toHaveLength(1); + expect(result[0].refPath).toBe('src/example.ts'); + }); + }); + + describe('validateCodeRef', () => { + // beforeEachは親のdescribeブロックで設定されているものを使用 + + it('有効なファイル参照では空のエラー配列を返すこと', () => { + const ref: CodeRef = { + fullMatch: '', + refPath: 'src/example.ts', + startLine: null, + endLine: null, + docFile: '/docs/test.md', + }; + + mockedFs.existsSync.mockReturnValue(true); + + const result = validateCodeRef(ref); + + expect(result).toEqual([]); + }); + + it('存在しないファイルでFILE_NOT_FOUNDエラーを返すこと', () => { + const ref: CodeRef = { + fullMatch: '', + refPath: 'src/missing.ts', + startLine: null, + endLine: null, + docFile: '/docs/test.md', + }; + + mockedFs.existsSync.mockReturnValue(false); + + const result = validateCodeRef(ref); + + expect(result).toHaveLength(1); + expect(result[0].type).toBe('FILE_NOT_FOUND'); + expect(result[0].message).toBe('参照先のファイルが見つかりません: src/missing.ts'); + }); + + it('パストラバーサルでPATH_TRAVERSALエラーを返すこと', () => { + const ref: CodeRef = { + fullMatch: '', + refPath: '../../../etc/passwd', + startLine: null, + endLine: null, + docFile: '/docs/test.md', + }; + + // 一時的にモックを上書きして、プロジェクトルート外のパスを返す + mockedPath.resolve.mockImplementationOnce(() => '/etc/passwd'); + + const result = validateCodeRef(ref); + + expect(result).toHaveLength(1); + expect(result[0].type).toBe('PATH_TRAVERSAL'); + }); + + it('無効な開始行番号でINVALID_LINE_NUMBERエラーを返すこと', () => { + const ref: CodeRef = { + fullMatch: '', + refPath: 'src/example.ts', + startLine: 0, + endLine: 5, + docFile: '/docs/test.md', + }; + + mockedFs.existsSync.mockReturnValue(true); + mockedFs.readFileSync.mockReturnValue('line1\nline2\nline3\nline4\nline5\n'); + + const result = validateCodeRef(ref); + + expect(result).toHaveLength(1); + expect(result[0].type).toBe('INVALID_LINE_NUMBER'); + }); + + it('終了行が総行数を超える場合にLINE_OUT_OF_RANGEエラーを返すこと', () => { + const ref: CodeRef = { + fullMatch: '', + refPath: 'src/example.ts', + startLine: 1, + endLine: 10, + docFile: '/docs/test.md', + }; + + mockedFs.existsSync.mockReturnValue(true); + mockedFs.readFileSync.mockReturnValue('line1\nline2\nline3\n'); // 3行のみ + + const result = validateCodeRef(ref); + + expect(result).toHaveLength(1); + expect(result[0].type).toBe('LINE_OUT_OF_RANGE'); + }); + + it('開始行が終了行より大きい場合にINVALID_RANGEエラーを返すこと', () => { + const ref: CodeRef = { + fullMatch: '', + refPath: 'src/example.ts', + startLine: 10, + endLine: 5, + docFile: '/docs/test.md', + }; + + mockedFs.existsSync.mockReturnValue(true); + mockedFs.readFileSync.mockReturnValue( + 'line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9\nline10\nline11\n' + ); + + const result = validateCodeRef(ref); + + expect(result).toHaveLength(1); + expect(result[0].type).toBe('INVALID_RANGE'); + }); + + it('ファイル読み込みエラーでREAD_ERRORエラーを返すこと', () => { + const ref: CodeRef = { + fullMatch: '', + refPath: 'src/example.ts', + startLine: 1, + endLine: 5, + docFile: '/docs/test.md', + }; + + mockedFs.existsSync.mockReturnValue(true); + mockedFs.readFileSync.mockImplementation(() => { + throw new Error('Permission denied'); + }); + + const result = validateCodeRef(ref); + + expect(result).toHaveLength(1); + expect(result[0].type).toBe('READ_ERROR'); + expect(result[0].message).toBe('ファイルの読み込みに失敗しました: Permission denied'); + }); + + it('有効な行範囲指定では空のエラー配列を返すこと(コードブロックがない場合)', () => { + const ref: CodeRef = { + fullMatch: '', + refPath: 'src/example.ts', + startLine: 1, + endLine: 3, + docFile: '/docs/test.md', + // codeBlockがundefinedの場合、コード内容検証はCODE_BLOCK_MISSINGを返す + }; + + mockedFs.existsSync.mockReturnValue(true); + mockedFs.readFileSync.mockReturnValue('line1\nline2\nline3\nline4\nline5\n'); + + const result = validateCodeRef(ref); + + // 既存の検証(ファイル存在、行範囲)は通過するが、 + // コードブロックがないため CODE_BLOCK_MISSING が発生する + expect(result).toHaveLength(1); + expect(result[0].type).toBe('CODE_BLOCK_MISSING'); + }); + }); + + describe('統合テスト', () => { + it('複数のエラータイプを同時に検出すること', () => { + // パストラバーサル + ファイル不存在の組み合わせ + const ref: CodeRef = { + fullMatch: '', + refPath: '../../../etc/passwd', + startLine: 1, + endLine: 5, + docFile: '/docs/test.md', + }; + + // 一時的にモックを上書きして、プロジェクトルート外のパスを返す + mockedPath.resolve.mockImplementationOnce(() => '/etc/passwd'); + + const result = validateCodeRef(ref); + + expect(result).toHaveLength(1); + expect(result[0].type).toBe('PATH_TRAVERSAL'); + }); + + it('CODE_REF正規表現パターンが正しく動作すること', () => { + const testCases = [ + { + input: '', + expected: { refPath: 'src/example.ts', startLine: null, endLine: null }, + }, + { + input: '', + expected: { refPath: 'src/example.ts', startLine: 10, endLine: 20 }, + }, + { + input: '', + expected: { refPath: 'src/example.ts', startLine: 5, endLine: 15 }, + }, + { + input: '', + expected: { refPath: 'src/example.ts', startLine: null, endLine: null }, + }, + ]; + + testCases.forEach(({ input, expected }) => { + const result = extractCodeRefs(input, '/test.md'); + expect(result).toHaveLength(1); + expect(result[0].refPath).toBe(expected.refPath); + expect(result[0].startLine).toBe(expected.startLine); + expect(result[0].endLine).toBe(expected.endLine); + }); + }); + + it('エッジケースの正規表現パターンを正しく処理すること', () => { + const testCases = [ + { + input: '', + expected: { refPath: 'src/example.ts', startLine: null, endLine: null }, + }, + { + input: '', + expected: { refPath: 'src/example.ts', startLine: 10, endLine: 20 }, + }, + { + input: '', + expected: { refPath: 'src/example.ts', startLine: 5, endLine: 15 }, + }, + { + input: '', + expected: { refPath: 'src/example.ts', startLine: null, endLine: null }, + }, + { + input: '', + expected: { refPath: 'path/with spaces/file.ts', startLine: 1, endLine: 2 }, + }, + { + input: '', + expected: { refPath: 'file-with-dashes.ts', startLine: null, endLine: null }, + }, + ]; + + testCases.forEach(({ input, expected }) => { + const result = extractCodeRefs(input, '/test.md'); + expect(result).toHaveLength(1); + expect(result[0].refPath).toBe(expected.refPath); + expect(result[0].startLine).toBe(expected.startLine); + expect(result[0].endLine).toBe(expected.endLine); + }); + }); + + it('複数のCODE_REFが同一ドキュメント内にある場合を処理すること', () => { + const content = ` +# ドキュメント + + + + + +テキストの間に + + + `; + + const result = extractCodeRefs(content, '/docs/multiple.md'); + + expect(result).toHaveLength(4); + expect(result[0].refPath).toBe('src/a.ts'); + expect(result[1].refPath).toBe('src/b.ts'); + expect(result[2].refPath).toBe('src/c.js'); + expect(result[3].refPath).toBe('src/d.ts'); + }); + }); + + describe('コード内容検証', () => { + beforeEach(() => { + // existsSyncは常にtrueを返す + mockedFs.existsSync.mockReturnValue(true); + }); + + it('CODE_LOCATION_MISMATCH: コードは一致するが行数が異なる場合', () => { + const ref: CodeRef = { + fullMatch: '', + refPath: 'src/example.ts', + startLine: 5, + endLine: 7, + docFile: '/docs/test.md', + codeBlock: 'const x = 1;\nconst y = 2;\nconst z = 3;', + }; + + const fileContent = `line1 +line2 +const x = 1; +const y = 2; +const z = 3; +line6 +line7 +line8`; + + mockedFs.readFileSync.mockReturnValue(fileContent); + + const result = validateCodeRef(ref); + + expect(result).toHaveLength(1); + expect(result[0].type).toBe('CODE_LOCATION_MISMATCH'); + expect(result[0].message).toContain('src/example.ts'); + expect(result[0].message).toContain('expect: 5-7'); + expect(result[0].message).toContain('result: 3-5'); + expect(result[0].suggestedLines).toEqual({ start: 3, end: 5 }); + }); + + it('CODE_CONTENT_MISMATCH: 指定行のコードが異なる場合', () => { + const ref: CodeRef = { + fullMatch: '', + refPath: 'src/example.ts', + startLine: 2, + endLine: 3, + docFile: '/docs/test.md', + codeBlock: 'const x = 1;\nconst y = 2;', + }; + + const fileContent = `line1 +const a = 100; +const b = 200; +line4`; + + mockedFs.readFileSync.mockReturnValue(fileContent); + + const result = validateCodeRef(ref); + + expect(result).toHaveLength(1); + expect(result[0].type).toBe('CODE_CONTENT_MISMATCH'); + expect(result[0].message).toContain('src/example.ts'); + expect(result[0].message).toContain('コードが一致しません'); + }); + + it('CODE_BLOCK_MISSING: CODE_REFの後にコードブロックがない場合', () => { + const ref: CodeRef = { + fullMatch: '', + refPath: 'src/example.ts', + startLine: 1, + endLine: 3, + docFile: '/docs/test.md', + // codeBlockがundefined + }; + + mockedFs.readFileSync.mockReturnValue('line1\nline2\nline3'); + + const result = validateCodeRef(ref); + + expect(result).toHaveLength(1); + expect(result[0].type).toBe('CODE_BLOCK_MISSING'); + expect(result[0].message).toContain('の後にコードブロックが見つかりません'); + expect(result[0].message).toContain('src/example.ts'); + }); + + it('複数の一致がファイル内にある場合、最初の出現を提案すること', () => { + const ref: CodeRef = { + fullMatch: '', + refPath: 'src/example.ts', + startLine: 10, + endLine: 11, + docFile: '/docs/test.md', + codeBlock: 'const x = 1;\nconst y = 2;', + }; + + const fileContent = `const x = 1; +const y = 2; +line3 +const x = 1; +const y = 2; +line6 +const x = 1; +const y = 2; +line9 +line10 +line11 +line12`; + + mockedFs.readFileSync.mockReturnValue(fileContent); + + const result = validateCodeRef(ref); + + // LINE_OUT_OF_RANGEとCODE_LOCATION_MISMATCHの両方が発生する可能性がある + // ここでは CODE_LOCATION_MISMATCH をチェック + const locationMismatch = result.find((e) => e.type === 'CODE_LOCATION_MISMATCH'); + expect(locationMismatch).toBeDefined(); + expect(locationMismatch!.message).toContain('コードは3箇所で見つかりました'); + expect(locationMismatch!.message).toContain('result: 1-2'); + expect(locationMismatch!.suggestedLines).toEqual({ start: 1, end: 2 }); + }); + + it('全体参照(行数なし)の場合、コード検証をスキップすること', () => { + const ref: CodeRef = { + fullMatch: '', + refPath: 'src/example.ts', + startLine: null, + endLine: null, + docFile: '/docs/test.md', + codeBlock: 'some code', + }; + + const result = validateCodeRef(ref); + + expect(result).toEqual([]); + }); + + it('コード内容が完全に一致する場合、エラーを返さないこと', () => { + const ref: CodeRef = { + fullMatch: '', + refPath: 'src/example.ts', + startLine: 2, + endLine: 4, + docFile: '/docs/test.md', + codeBlock: 'const x = 1;\nconst y = 2;\nconst z = 3;', + }; + + const fileContent = `line1 +const x = 1; +const y = 2; +const z = 3; +line5`; + + mockedFs.readFileSync.mockReturnValue(fileContent); + + const result = validateCodeRef(ref); + + expect(result).toEqual([]); + }); + + it('空白の違いを吸収してコードが一致する場合、エラーを返さないこと', () => { + const ref: CodeRef = { + fullMatch: '', + refPath: 'src/example.ts', + startLine: 2, + endLine: 3, + docFile: '/docs/test.md', + codeBlock: 'const x = 1;\nconst y = 2;', + }; + + const fileContent = `line1 + const x = 1; + const y = 2; +line4`; + + mockedFs.readFileSync.mockReturnValue(fileContent); + + const result = validateCodeRef(ref); + + expect(result).toEqual([]); + }); + + // 新規テスト: シンボル指定のみでコードブロックなし + it('シンボル指定のみでコードブロックがない場合、CODE_BLOCK_MISSINGエラーを返すこと', () => { + const ref: CodeRef = { + fullMatch: '', + refPath: 'src/example.ts', + symbolPath: 'myFunction', + memberName: 'myFunction', + startLine: null, + endLine: null, + docFile: '/docs/test.md', + // codeBlockがundefined + }; + + mockedFs.existsSync.mockReturnValue(true); + mockedFs.readFileSync.mockReturnValue('function myFunction() { return 42; }'); + + const result = validateCodeRef(ref); + + expect(result).toHaveLength(1); + expect(result[0].type).toBe('CODE_BLOCK_MISSING'); + expect(result[0].message).toContain('シンボル指定のCODE_REF'); + expect(result[0].message).toContain('src/example.ts#myFunction'); + }); + + it('シンボル指定のみで空のコードブロックがある場合、CODE_BLOCK_MISSINGエラーを返すこと', () => { + const ref: CodeRef = { + fullMatch: '', + refPath: 'src/example.ts', + symbolPath: 'myFunction', + memberName: 'myFunction', + startLine: null, + endLine: null, + docFile: '/docs/test.md', + codeBlock: ' \n\n ', // 空白のみ + }; + + mockedFs.existsSync.mockReturnValue(true); + + const result = validateCodeRef(ref); + + expect(result).toHaveLength(1); + expect(result[0].type).toBe('CODE_BLOCK_MISSING'); + }); + + it('ファイル全体参照でコードブロックがなくてもエラーを返さないこと', () => { + const ref: CodeRef = { + fullMatch: '', + refPath: 'src/example.ts', + startLine: null, + endLine: null, + docFile: '/docs/test.md', + // シンボル指定なし、コードブロックもなし + }; + + mockedFs.existsSync.mockReturnValue(true); + + const result = validateCodeRef(ref); + + expect(result).toEqual([]); + }); + + it('シンボル指定のみでコードブロックがある場合、検証が実行されること', () => { + const ref: CodeRef = { + fullMatch: '', + refPath: 'src/example.ts', + symbolPath: 'myFunction', + memberName: 'myFunction', + startLine: null, + endLine: null, + docFile: '/docs/test.md', + codeBlock: 'function myFunction() {\n return 42;\n}', + }; + + mockedFs.existsSync.mockReturnValue(true); + // コードブロックがある場合は、シンボル全体との比較が行われる + // この検証は既存のロジックで実装されている + const result = validateCodeRef(ref); + + // エラーがないか、またはシンボルが見つからないなどの別のエラー + // 既存のロジックに依存するため、ここでは詳細な検証は行わない + expect(Array.isArray(result)).toBe(true); + }); + }); + + describe('extractCodeRefs - シンボル記法', () => { + it('クラス名+メソッド名のシンボルパスをパースすること', () => { + const content = ` +# Test Document + + +`; + + const result = extractCodeRefs(content, '/docs/test.md'); + + expect(result).toHaveLength(1); + expect(result[0]).toMatchObject({ + refPath: 'backend/src/services/evaluation.service.ts', + symbolPath: 'EvaluationService#evaluateDesign', + className: 'EvaluationService', + memberName: 'evaluateDesign', + startLine: 30, + endLine: 172, + }); + }); + + it('関数名のみのシンボルパスをパースすること', () => { + const content = ` +# Test Document + + +`; + + const result = extractCodeRefs(content, '/docs/test.md'); + + expect(result).toHaveLength(1); + expect(result[0]).toMatchObject({ + refPath: 'backend/src/utils/helper.ts', + symbolPath: 'helperFunction', + memberName: 'helperFunction', + startLine: 10, + endLine: 20, + }); + expect(result[0].className).toBeUndefined(); + }); + + it('シンボルパスのみ(行番号なし)をパースすること', () => { + const content = ` +# Test Document + + +`; + + const result = extractCodeRefs(content, '/docs/test.md'); + + expect(result).toHaveLength(1); + expect(result[0]).toMatchObject({ + refPath: 'backend/src/services/evaluation.service.ts', + symbolPath: 'EvaluationService#evaluateDesign', + className: 'EvaluationService', + memberName: 'evaluateDesign', + startLine: null, + endLine: null, + }); + }); + + it('既存の記法(行番号のみ)も引き続き動作すること', () => { + const content = ` +# Test Document + + +`; + + const result = extractCodeRefs(content, '/docs/test.md'); + + expect(result).toHaveLength(1); + expect(result[0]).toMatchObject({ + refPath: 'backend/src/services/evaluation.service.ts', + startLine: 30, + endLine: 172, + }); + expect(result[0].symbolPath).toBeUndefined(); + expect(result[0].className).toBeUndefined(); + expect(result[0].memberName).toBeUndefined(); + }); + + it('シンボル記法と行番号記法を混在させられること', () => { + const content = ` +# Test Document + + + + +`; + + const result = extractCodeRefs(content, '/docs/test.md'); + + expect(result).toHaveLength(2); + expect(result[0].symbolPath).toBe('EvaluationService#evaluateDesign'); + expect(result[1].symbolPath).toBeUndefined(); + }); + }); + + describe('validateSymbolRef', () => { + it('TypeScript/JavaScript以外のファイルでエラーを返すこと', () => { + const ref: CodeRef = { + fullMatch: '', + refPath: 'README.md', + symbolPath: 'MyClass#method', + className: 'MyClass', + memberName: 'method', + startLine: null, + endLine: null, + docFile: '/docs/test.md', + }; + + mockedFs.existsSync.mockReturnValue(true); + + const result = validateCodeRef(ref); + + expect(result).toHaveLength(1); + expect(result[0].type).toBe('NOT_TYPESCRIPT_FILE'); + expect(result[0].message).toContain('TypeScript/JavaScript'); + }); + }); +}); From b6638ef47b0c9f7019f8271776d01e041cda3969 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Mon, 29 Dec 2025 21:23:02 +0900 Subject: [PATCH 06/57] feat: integrate configuration system into validate.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Integrate the configuration system into validate.ts: - Add optional config parameter to validateCodeRef, validateCodeContent, and validateSymbolRef - Update main() function to load and use configuration - Replace hardcoded DOCS_DIR, PROJECT_ROOT, DOCSIGNORE_FILE with config values - Update resolveTargetFiles to accept config parameter - Update all test calls to pass mockConfig All 202 tests passing successfully. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/core/validate.ts | 64 ++++++++++++++++++++++++++----------------- test/validate.test.ts | 51 ++++++++++++++++++++-------------- 2 files changed, 69 insertions(+), 46 deletions(-) diff --git a/src/core/validate.ts b/src/core/validate.ts index 3e5e7ef..451aca3 100644 --- a/src/core/validate.ts +++ b/src/core/validate.ts @@ -29,11 +29,9 @@ import { displayCodeDiff, displayLineRangeDiff } from '../utils/diff-display'; import { isIgnored, loadDocsignorePatterns } from '../utils/ignore-pattern'; import { associateCodeBlocksWithRefs } from '../utils/markdown'; import type { CodeRef, CodeRefError } from '../utils/types'; +import { loadConfig, getDocsPath, getIgnoreFilePath, type CodeRefConfig } from '../config'; -// 設定 -const DOCS_DIR = path.join(__dirname, '../..', 'docs'); -const PROJECT_ROOT = path.join(__dirname, '../..'); -const DOCSIGNORE_FILE = path.join(PROJECT_ROOT, '.docsignore'); +// CODE_REF パターン定数 const CODE_REF_PATTERN = //g; // コマンドライン引数のパース @@ -167,7 +165,9 @@ export function extractCodeRefs(content: string, filePath: string): CodeRef[] { /** * コード内容の検証 */ -export function validateCodeContent(ref: CodeRef): CodeRefError[] { +export function validateCodeContent(ref: CodeRef, config?: CodeRefConfig): CodeRefError[] { + const cfg = config || loadConfig(); + const projectRoot = cfg.projectRoot; const errors: CodeRefError[] = []; // 行数指定がない場合の処理 @@ -185,7 +185,7 @@ export function validateCodeContent(ref: CodeRef): CodeRefError[] { } // シンボルの範囲を取得 - const absolutePath = path.resolve(PROJECT_ROOT, ref.refPath); + const absolutePath = path.resolve(projectRoot, ref.refPath); const fileContent = fs.readFileSync(absolutePath, 'utf-8'); const matches = findSymbolInAST(fileContent, absolutePath, { className: ref.className, @@ -231,7 +231,7 @@ export function validateCodeContent(ref: CodeRef): CodeRefError[] { return errors; } - const absolutePath = path.resolve(PROJECT_ROOT, ref.refPath); + const absolutePath = path.resolve(projectRoot, ref.refPath); try { // 実ファイルから指定行のコードを取得 @@ -280,7 +280,9 @@ export function validateCodeContent(ref: CodeRef): CodeRefError[] { /** * シンボル指定の検証 */ -export function validateSymbolRef(ref: CodeRef): CodeRefError[] { +export function validateSymbolRef(ref: CodeRef, config?: CodeRefConfig): CodeRefError[] { + const cfg = config || loadConfig(); + const projectRoot = cfg.projectRoot; const errors: CodeRefError[] = []; // シンボル指定がない場合はスキップ @@ -288,7 +290,7 @@ export function validateSymbolRef(ref: CodeRef): CodeRefError[] { return errors; } - const absolutePath = path.resolve(PROJECT_ROOT, ref.refPath); + const absolutePath = path.resolve(projectRoot, ref.refPath); // TypeScript/JavaScriptファイルチェック if (!isTypeScriptOrJavaScript(absolutePath)) { @@ -359,14 +361,16 @@ export function validateSymbolRef(ref: CodeRef): CodeRefError[] { /** * 参照先のファイルと行番号の存在を確認 */ -export function validateCodeRef(ref: CodeRef): CodeRefError[] { +export function validateCodeRef(ref: CodeRef, config?: CodeRefConfig): CodeRefError[] { + const cfg = config || loadConfig(); + const projectRoot = cfg.projectRoot; const errors: CodeRefError[] = []; // 相対パスを絶対パスに変換(プロジェクトルートからの相対パス) - const absolutePath = path.resolve(PROJECT_ROOT, ref.refPath); + const absolutePath = path.resolve(projectRoot, ref.refPath); // パストラバーサル攻撃を防ぐ: プロジェクトルート内に留まるか検証 - if (!absolutePath.startsWith(PROJECT_ROOT + path.sep)) { + if (!absolutePath.startsWith(projectRoot + path.sep)) { errors.push({ type: 'PATH_TRAVERSAL', message: `参照先のパスがプロジェクトルート外を指しています: ${ref.refPath}`, @@ -427,13 +431,13 @@ export function validateCodeRef(ref: CodeRef): CodeRefError[] { // シンボル指定がある場合はシンボルバリデーション if (errors.length === 0 && ref.symbolPath) { - const symbolErrors = validateSymbolRef(ref); + const symbolErrors = validateSymbolRef(ref, cfg); errors.push(...symbolErrors); } // コード内容の検証(既存のエラーがない場合のみ) if (errors.length === 0) { - const contentErrors = validateCodeContent(ref); + const contentErrors = validateCodeContent(ref, cfg); errors.push(...contentErrors); } @@ -456,17 +460,20 @@ function isDirectory(filePath: string): boolean { * - ファイル指定がない場合: 全ファイル * - ファイル指定がある場合: 指定されたファイル/ディレクトリのみ */ -function resolveTargetFiles(targets: string[]): string[] { +function resolveTargetFiles(targets: string[], config: CodeRefConfig): string[] { + const docsPath = getDocsPath(config); + const projectRoot = config.projectRoot; + if (targets.length === 0) { // ファイル指定がない場合は全ファイルを対象 - return findMarkdownFiles(DOCS_DIR); + return findMarkdownFiles(docsPath); } const resolvedFiles = new Set(); for (const target of targets) { // 相対パスを絶対パスに変換 - const absolutePath = path.isAbsolute(target) ? target : path.join(PROJECT_ROOT, target); + const absolutePath = path.isAbsolute(target) ? target : path.join(projectRoot, target); if (isDirectory(absolutePath)) { // ディレクトリの場合は再帰的にマークダウンファイルを検索 @@ -488,22 +495,29 @@ function resolveTargetFiles(targets: string[]): string[] { export async function main(): Promise { console.log('🔍 ドキュメント内のコード参照を検証しています...\n'); + // 設定を読み込み + const config = loadConfig({ + targets: targetFiles.length > 0 ? targetFiles : undefined, + verbose, + }); + // 対象ファイルを解決 - const allMarkdownFiles = resolveTargetFiles(targetFiles); + const allMarkdownFiles = resolveTargetFiles(targetFiles, config); if (targetFiles.length > 0 && verbose) { console.log(`📋 指定されたファイル/ディレクトリ: ${targetFiles.join(', ')}\n`); } // .docsignoreパターンを読み込み - const ignorePatterns = loadDocsignorePatterns(DOCSIGNORE_FILE); + const ignoreFilePath = getIgnoreFilePath(config); + const ignorePatterns = ignoreFilePath ? loadDocsignorePatterns(ignoreFilePath) : []; if (verbose) { console.log(`📋 .docsignoreから${ignorePatterns.length}個のパターンを読み込みました\n`); } // .docsignoreで除外されていないファイルのみを対象とする const markdownFiles = allMarkdownFiles.filter((file) => { - const relativePath = path.relative(PROJECT_ROOT, file); + const relativePath = path.relative(config.projectRoot, file); return !isIgnored(relativePath, ignorePatterns); }); @@ -528,7 +542,7 @@ export async function main(): Promise { allRefs.push(...refs); if (verbose) { - console.log(` ${path.relative(DOCS_DIR, file)}: ${refs.length} 個の参照`); + console.log(` ${path.relative(getDocsPath(config), file)}: ${refs.length} 個の参照`); } } } @@ -541,7 +555,7 @@ export async function main(): Promise { } // 各参照を検証 - const allErrors = await Promise.all(allRefs.map((ref) => validateCodeRef(ref))).then((results) => + const allErrors = await Promise.all(allRefs.map((ref) => validateCodeRef(ref, config))).then((results) => results.flat() ); @@ -556,7 +570,7 @@ export async function main(): Promise { const errorsByDoc: Record = {}; for (const error of allErrors) { - const docFile = path.relative(PROJECT_ROOT, error.ref.docFile); + const docFile = path.relative(config.projectRoot, error.ref.docFile); if (!errorsByDoc[docFile]) { errorsByDoc[docFile] = []; @@ -573,14 +587,14 @@ export async function main(): Promise { console.log(` ❌ ${error.type}: ${error.message}`); // ドキュメント内の行番号を表示 - const filePath = path.relative(PROJECT_ROOT, error.ref.docFile); + const filePath = path.relative(config.projectRoot, error.ref.docFile); const lineInfo = error.ref.docLineNumber ? `:${error.ref.docLineNumber}` : ''; console.log(` ${filePath}${lineInfo}: ${error.ref.fullMatch}`); // CODE_LOCATION_MISMATCHの場合、行範囲の差分を表示 if (error.type === 'CODE_LOCATION_MISMATCH' && error.suggestedLines && verbose) { // verboseモードでは詳細な差分を表示 - const filePath = path.join(PROJECT_ROOT, error.ref.refPath); + const filePath = path.join(config.projectRoot, error.ref.refPath); const actualCode = extractLinesFromFile( filePath, error.suggestedLines.start, diff --git a/test/validate.test.ts b/test/validate.test.ts index 7accf1c..32c220d 100644 --- a/test/validate.test.ts +++ b/test/validate.test.ts @@ -5,10 +5,19 @@ import { jest } from '@jest/globals'; import type { CodeRef } from '../src/utils/types'; import { extractCodeRefs, findMarkdownFiles, validateCodeRef } from '../src/core/validate'; +import type { CodeRefConfig } from '../src/config'; // テスト用のモックプロジェクトルート const mockProjectRoot = '/project'; +// テスト用の設定オブジェクト +const mockConfig: CodeRefConfig = { + projectRoot: mockProjectRoot, + docsDir: 'docs', + ignoreFile: '.docsignore', + verbose: false, +}; + // モックの設定 jest.mock('fs'); jest.mock('path', () => { @@ -170,7 +179,7 @@ describe('validate-docs-code', () => { mockedFs.existsSync.mockReturnValue(true); - const result = validateCodeRef(ref); + const result = validateCodeRef(ref, mockConfig); expect(result).toEqual([]); }); @@ -186,7 +195,7 @@ describe('validate-docs-code', () => { mockedFs.existsSync.mockReturnValue(false); - const result = validateCodeRef(ref); + const result = validateCodeRef(ref, mockConfig); expect(result).toHaveLength(1); expect(result[0].type).toBe('FILE_NOT_FOUND'); @@ -205,7 +214,7 @@ describe('validate-docs-code', () => { // 一時的にモックを上書きして、プロジェクトルート外のパスを返す mockedPath.resolve.mockImplementationOnce(() => '/etc/passwd'); - const result = validateCodeRef(ref); + const result = validateCodeRef(ref, mockConfig); expect(result).toHaveLength(1); expect(result[0].type).toBe('PATH_TRAVERSAL'); @@ -223,7 +232,7 @@ describe('validate-docs-code', () => { mockedFs.existsSync.mockReturnValue(true); mockedFs.readFileSync.mockReturnValue('line1\nline2\nline3\nline4\nline5\n'); - const result = validateCodeRef(ref); + const result = validateCodeRef(ref, mockConfig); expect(result).toHaveLength(1); expect(result[0].type).toBe('INVALID_LINE_NUMBER'); @@ -241,7 +250,7 @@ describe('validate-docs-code', () => { mockedFs.existsSync.mockReturnValue(true); mockedFs.readFileSync.mockReturnValue('line1\nline2\nline3\n'); // 3行のみ - const result = validateCodeRef(ref); + const result = validateCodeRef(ref, mockConfig); expect(result).toHaveLength(1); expect(result[0].type).toBe('LINE_OUT_OF_RANGE'); @@ -261,7 +270,7 @@ describe('validate-docs-code', () => { 'line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9\nline10\nline11\n' ); - const result = validateCodeRef(ref); + const result = validateCodeRef(ref, mockConfig); expect(result).toHaveLength(1); expect(result[0].type).toBe('INVALID_RANGE'); @@ -281,7 +290,7 @@ describe('validate-docs-code', () => { throw new Error('Permission denied'); }); - const result = validateCodeRef(ref); + const result = validateCodeRef(ref, mockConfig); expect(result).toHaveLength(1); expect(result[0].type).toBe('READ_ERROR'); @@ -301,7 +310,7 @@ describe('validate-docs-code', () => { mockedFs.existsSync.mockReturnValue(true); mockedFs.readFileSync.mockReturnValue('line1\nline2\nline3\nline4\nline5\n'); - const result = validateCodeRef(ref); + const result = validateCodeRef(ref, mockConfig); // 既存の検証(ファイル存在、行範囲)は通過するが、 // コードブロックがないため CODE_BLOCK_MISSING が発生する @@ -324,7 +333,7 @@ describe('validate-docs-code', () => { // 一時的にモックを上書きして、プロジェクトルート外のパスを返す mockedPath.resolve.mockImplementationOnce(() => '/etc/passwd'); - const result = validateCodeRef(ref); + const result = validateCodeRef(ref, mockConfig); expect(result).toHaveLength(1); expect(result[0].type).toBe('PATH_TRAVERSAL'); @@ -446,7 +455,7 @@ line8`; mockedFs.readFileSync.mockReturnValue(fileContent); - const result = validateCodeRef(ref); + const result = validateCodeRef(ref, mockConfig); expect(result).toHaveLength(1); expect(result[0].type).toBe('CODE_LOCATION_MISMATCH'); @@ -473,7 +482,7 @@ line4`; mockedFs.readFileSync.mockReturnValue(fileContent); - const result = validateCodeRef(ref); + const result = validateCodeRef(ref, mockConfig); expect(result).toHaveLength(1); expect(result[0].type).toBe('CODE_CONTENT_MISMATCH'); @@ -493,7 +502,7 @@ line4`; mockedFs.readFileSync.mockReturnValue('line1\nline2\nline3'); - const result = validateCodeRef(ref); + const result = validateCodeRef(ref, mockConfig); expect(result).toHaveLength(1); expect(result[0].type).toBe('CODE_BLOCK_MISSING'); @@ -526,7 +535,7 @@ line12`; mockedFs.readFileSync.mockReturnValue(fileContent); - const result = validateCodeRef(ref); + const result = validateCodeRef(ref, mockConfig); // LINE_OUT_OF_RANGEとCODE_LOCATION_MISMATCHの両方が発生する可能性がある // ここでは CODE_LOCATION_MISMATCH をチェック @@ -547,7 +556,7 @@ line12`; codeBlock: 'some code', }; - const result = validateCodeRef(ref); + const result = validateCodeRef(ref, mockConfig); expect(result).toEqual([]); }); @@ -570,7 +579,7 @@ line5`; mockedFs.readFileSync.mockReturnValue(fileContent); - const result = validateCodeRef(ref); + const result = validateCodeRef(ref, mockConfig); expect(result).toEqual([]); }); @@ -592,7 +601,7 @@ line4`; mockedFs.readFileSync.mockReturnValue(fileContent); - const result = validateCodeRef(ref); + const result = validateCodeRef(ref, mockConfig); expect(result).toEqual([]); }); @@ -613,7 +622,7 @@ line4`; mockedFs.existsSync.mockReturnValue(true); mockedFs.readFileSync.mockReturnValue('function myFunction() { return 42; }'); - const result = validateCodeRef(ref); + const result = validateCodeRef(ref, mockConfig); expect(result).toHaveLength(1); expect(result[0].type).toBe('CODE_BLOCK_MISSING'); @@ -635,7 +644,7 @@ line4`; mockedFs.existsSync.mockReturnValue(true); - const result = validateCodeRef(ref); + const result = validateCodeRef(ref, mockConfig); expect(result).toHaveLength(1); expect(result[0].type).toBe('CODE_BLOCK_MISSING'); @@ -653,7 +662,7 @@ line4`; mockedFs.existsSync.mockReturnValue(true); - const result = validateCodeRef(ref); + const result = validateCodeRef(ref, mockConfig); expect(result).toEqual([]); }); @@ -673,7 +682,7 @@ line4`; mockedFs.existsSync.mockReturnValue(true); // コードブロックがある場合は、シンボル全体との比較が行われる // この検証は既存のロジックで実装されている - const result = validateCodeRef(ref); + const result = validateCodeRef(ref, mockConfig); // エラーがないか、またはシンボルが見つからないなどの別のエラー // 既存のロジックに依存するため、ここでは詳細な検証は行わない @@ -794,7 +803,7 @@ line4`; mockedFs.existsSync.mockReturnValue(true); - const result = validateCodeRef(ref); + const result = validateCodeRef(ref, mockConfig); expect(result).toHaveLength(1); expect(result[0].type).toBe('NOT_TYPESCRIPT_FILE'); From 6192ccdeec20c3b783b606c0de779463fad56066 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Mon, 29 Dec 2025 21:27:53 +0900 Subject: [PATCH 07/57] feat: integrate configuration system into fix.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Integrate the configuration system into fix.ts: - Import loadFixConfig, getDocsPath, and CodeRefFixConfig from config - Remove hardcoded DOCS_DIR and PROJECT_ROOT constants - Update collectErrors() to accept config parameter - Update main() to load config using loadFixConfig() - Replace all DOCS_DIR and PROJECT_ROOT references with config values - Pass config to validateCodeRef() calls All 202 tests passing successfully. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/core/fix.ts | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/core/fix.ts b/src/core/fix.ts index c5e553e..ca5054b 100644 --- a/src/core/fix.ts +++ b/src/core/fix.ts @@ -18,10 +18,7 @@ import { applyFix, createFixAction, handleMultipleMatches, isFixableError } from import { askYesNo, createPromptInterface, displayFixPreview } from '../utils/prompt'; import type { CodeRefError, FixOptions, FixResult } from '../utils/types'; import { extractCodeRefs, findMarkdownFiles, validateCodeRef } from './validate'; - -// 設定 -const DOCS_DIR = path.join(__dirname, '../..', 'docs'); -const PROJECT_ROOT = path.join(__dirname, '../..'); +import { loadFixConfig, getDocsPath, type CodeRefFixConfig } from '../config'; // コマンドライン引数のパース function parseArgs(): FixOptions { @@ -46,8 +43,9 @@ interface ErrorGroup { /** * エラーを収集 */ -function collectErrors(): ErrorGroup[] { - const markdownFiles = findMarkdownFiles(DOCS_DIR); +function collectErrors(config: CodeRefFixConfig): ErrorGroup[] { + const docsPath = getDocsPath(config); + const markdownFiles = findMarkdownFiles(docsPath); const errorsByDoc: Record = {}; for (const file of markdownFiles) { @@ -55,7 +53,7 @@ function collectErrors(): ErrorGroup[] { const refs = extractCodeRefs(content, file); for (const ref of refs) { - const errors = validateCodeRef(ref); + const errors = validateCodeRef(ref, config); const fixableErrors = errors.filter(isFixableError); if (fixableErrors.length > 0) { @@ -79,6 +77,14 @@ function collectErrors(): ErrorGroup[] { async function main(): Promise { const options = parseArgs(); + // 設定を読み込み + const config = loadFixConfig({ + dryRun: options.dryRun, + auto: options.auto, + backup: !options.noBackup, + verbose: options.verbose, + }); + console.log('🔧 CODE_REFエラーの修正を開始します...\n'); if (options.dryRun) { @@ -86,7 +92,7 @@ async function main(): Promise { } // エラーを収集 - const errorGroups = collectErrors(); + const errorGroups = collectErrors(config); if (errorGroups.length === 0) { console.log('✅ 修正可能なエラーは見つかりませんでした'); @@ -104,7 +110,7 @@ async function main(): Promise { try { for (const group of errorGroups) { - console.log(`\n📄 ${path.relative(PROJECT_ROOT, group.docFile)}`); + console.log(`\n📄 ${path.relative(config.projectRoot, group.docFile)}`); console.log(` ${group.errors.length}個のエラー\n`); // エラーをdocLineNumber降順(下から上へ)にソート @@ -120,7 +126,7 @@ async function main(): Promise { for (const error of sortedErrors) { console.log(`\n❌ ${error.type}: ${error.message}`); console.log( - ` 参照: ${path.relative(PROJECT_ROOT, error.ref.docFile)}${error.ref.docLineNumber ? `:${error.ref.docLineNumber}` : ''}` + ` 参照: ${path.relative(config.projectRoot, error.ref.docFile)}${error.ref.docLineNumber ? `:${error.ref.docLineNumber}` : ''}` ); // 修正アクションを作成 @@ -251,7 +257,7 @@ async function main(): Promise { console.log(`\n💾 バックアップファイル: ${backupFiles.size}個`); for (const file of backupFiles) { const backupPath = `${file}.backup`; - console.log(` ${path.relative(PROJECT_ROOT, backupPath)}`); + console.log(` ${path.relative(config.projectRoot, backupPath)}`); } } From 3ec29f8b18f6128e85723df6315a76ed52baacf1 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Mon, 29 Dec 2025 21:41:45 +0900 Subject: [PATCH 08/57] feat: implement CLI with commander and programmatic API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 4: CLI Implementation completed - Separated CLI logic from core validation logic - Moved validate.ts CLI code to src/cli/validate.ts - Moved fix.ts entirely to src/cli/fix.ts (all CLI code) - Core validate.ts now only exports core functions - Created bin/coderef.js as CLI entry point using commander - Supports 'validate' and 'fix' commands - Proper help output and option handling - Created src/index.ts for programmatic API - Exports core validation functions - Exports configuration functions and types - Exports utility functions for AST and code comparison - Added commander dependency - Updated package.json exports to fix types order - All 202 tests passing - CLI tested and working locally 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- bin/coderef.js | 44 ++++++++ package-lock.json | 22 ++-- package.json | 7 +- src/{core => cli}/fix.ts | 2 +- src/cli/validate.ts | 218 +++++++++++++++++++++++++++++++++++++ src/core/validate.ts | 224 +-------------------------------------- src/index.ts | 67 ++++++++++++ 7 files changed, 352 insertions(+), 232 deletions(-) create mode 100755 bin/coderef.js rename src/{core => cli}/fix.ts (99%) create mode 100644 src/cli/validate.ts create mode 100644 src/index.ts diff --git a/bin/coderef.js b/bin/coderef.js new file mode 100755 index 0000000..61772d6 --- /dev/null +++ b/bin/coderef.js @@ -0,0 +1,44 @@ +#!/usr/bin/env node + +/** + * CLI entry point for @cawpea/coderef + */ + +const { program } = require('commander'); +const packageJson = require('../package.json'); + +program + .name('coderef') + .description('Validate and fix code references in markdown documentation') + .version(packageJson.version); + +program + .command('validate [files...]') + .description('Validate CODE_REF references in markdown files') + .option('-v, --verbose', 'Show detailed validation information') + .action(async (files, options) => { + const { main } = require('../dist/cli/validate.js'); + const args = []; + if (options.verbose) args.push('--verbose'); + args.push(...files); + await main(args); + }); + +program + .command('fix') + .description('Interactively fix CODE_REF errors') + .option('--dry-run', 'Show what would be fixed without making changes') + .option('--auto', 'Automatically apply all fixes without confirmation') + .option('--backup', 'Create backup files before applying fixes') + .option('-v, --verbose', 'Show detailed fix information') + .action(async (options) => { + const { main } = require('../dist/cli/fix.js'); + const args = []; + if (options.dryRun) args.push('--dry-run'); + if (options.auto) args.push('--auto'); + if (options.backup) args.push('--backup'); + if (options.verbose) args.push('--verbose'); + await main(args); + }); + +program.parse(); diff --git a/package-lock.json b/package-lock.json index b3ae194..8d23074 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "version": "0.1.0", "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "^8.50.1" + "@typescript-eslint/typescript-estree": "^8.50.1", + "commander": "^14.0.2" }, "bin": { "coderef": "bin/coderef.js" @@ -3372,13 +3373,12 @@ "license": "MIT" }, "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", "license": "MIT", "engines": { - "node": ">= 6" + "node": ">=20" } }, "node_modules/concat-map": { @@ -6261,6 +6261,16 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", diff --git a/package.json b/package.json index 7e30ba5..45a9cf7 100644 --- a/package.json +++ b/package.json @@ -20,9 +20,9 @@ }, "exports": { ".": { + "types": "./dist/index.d.ts", "import": "./dist/index.mjs", - "require": "./dist/index.js", - "types": "./dist/index.d.ts" + "require": "./dist/index.js" }, "./package.json": "./package.json" }, @@ -50,7 +50,8 @@ "node": ">=16.0.0" }, "dependencies": { - "@typescript-eslint/typescript-estree": "^8.50.1" + "@typescript-eslint/typescript-estree": "^8.50.1", + "commander": "^14.0.2" }, "devDependencies": { "@types/jest": "^30.0.0", diff --git a/src/core/fix.ts b/src/cli/fix.ts similarity index 99% rename from src/core/fix.ts rename to src/cli/fix.ts index ca5054b..aa30cf5 100644 --- a/src/core/fix.ts +++ b/src/cli/fix.ts @@ -17,7 +17,7 @@ import { createBackup } from '../utils/backup'; import { applyFix, createFixAction, handleMultipleMatches, isFixableError } from '../utils/fix'; import { askYesNo, createPromptInterface, displayFixPreview } from '../utils/prompt'; import type { CodeRefError, FixOptions, FixResult } from '../utils/types'; -import { extractCodeRefs, findMarkdownFiles, validateCodeRef } from './validate'; +import { extractCodeRefs, findMarkdownFiles, validateCodeRef } from '../core/validate'; import { loadFixConfig, getDocsPath, type CodeRefFixConfig } from '../config'; // コマンドライン引数のパース diff --git a/src/cli/validate.ts b/src/cli/validate.ts new file mode 100644 index 0000000..427638f --- /dev/null +++ b/src/cli/validate.ts @@ -0,0 +1,218 @@ +#!/usr/bin/env node + +/** + * CLI for validating CODE_REF references in markdown documentation + * + * Usage: + * coderef validate # Validate all files + * coderef validate --verbose # Verbose output + * coderef validate docs/README.md # Specific file + * coderef validate docs/backend/ # Specific directory + */ + +import * as fs from 'fs'; +import * as path from 'path'; + +import { loadConfig, getDocsPath, getIgnoreFilePath } from '../config'; +import { findMarkdownFiles, extractCodeRefs, validateCodeRef } from '../core/validate'; +import { isIgnored, loadDocsignorePatterns } from '../utils/ignore-pattern'; +import { displayLineRangeDiff } from '../utils/diff-display'; +import { extractLinesFromFile } from '../utils/code-comparison'; + +/** + * CLI options + */ +interface CliOptions { + verbose: boolean; + files: string[]; +} + +/** + * Parse command line arguments + */ +function parseCliArgs(args: string[]): CliOptions { + const verbose = args.includes('--verbose') || args.includes('-v'); + const files = args.filter((arg) => !arg.startsWith('-')); + + return { verbose, files }; +} + +/** + * Check if path is a directory + */ +function isDirectory(filePath: string): boolean { + try { + const stat = fs.statSync(filePath); + return stat.isDirectory(); + } catch { + return false; + } +} + +/** + * Resolve target files from CLI arguments + */ +function resolveTargetFiles(targets: string[], projectRoot: string, docsPath: string): string[] { + if (targets.length === 0) { + // No files specified, validate all files + return findMarkdownFiles(docsPath); + } + + const resolvedFiles = new Set(); + + for (const target of targets) { + // Convert relative path to absolute path + const absolutePath = path.isAbsolute(target) ? target : path.join(projectRoot, target); + + if (isDirectory(absolutePath)) { + // Directory: recursively find markdown files + const files = findMarkdownFiles(absolutePath); + files.forEach((file) => resolvedFiles.add(file)); + } else if (fs.existsSync(absolutePath)) { + // File: add directly + if (absolutePath.endsWith('.md')) { + resolvedFiles.add(absolutePath); + } + } else { + console.warn(`⚠️ File not found: ${target}`); + } + } + + return Array.from(resolvedFiles); +} + +/** + * Main CLI function + */ +export async function main(args: string[] = process.argv.slice(2)): Promise { + const options = parseCliArgs(args); + + console.log('🔍 Validating CODE_REF references in documentation...\n'); + + // Load configuration + const config = loadConfig({ + targets: options.files.length > 0 ? options.files : undefined, + verbose: options.verbose, + }); + + // Resolve target files + const docsPath = getDocsPath(config); + const allMarkdownFiles = resolveTargetFiles(options.files, config.projectRoot, docsPath); + + if (options.files.length > 0 && options.verbose) { + console.log(`📋 Specified files/directories: ${options.files.join(', ')}\n`); + } + + // Load .docsignore patterns + const ignoreFilePath = getIgnoreFilePath(config); + const ignorePatterns = ignoreFilePath ? loadDocsignorePatterns(ignoreFilePath) : []; + if (options.verbose) { + console.log(`📋 Loaded ${ignorePatterns.length} patterns from .docsignore\n`); + } + + // Filter files not excluded by .docsignore + const markdownFiles = allMarkdownFiles.filter((file) => { + const relativePath = path.relative(config.projectRoot, file); + return !isIgnored(relativePath, ignorePatterns); + }); + + if (options.verbose && allMarkdownFiles.length > markdownFiles.length) { + console.log( + `📋 ${allMarkdownFiles.length - markdownFiles.length} files excluded by .docsignore\n` + ); + } + + console.log(`📄 Found ${markdownFiles.length} markdown files\n`); + + // Extract all CODE_REF references + let totalRefs = 0; + const allRefs: Array<{ ref: any; file: string }> = []; + + for (const file of markdownFiles) { + const content = fs.readFileSync(file, 'utf-8'); + const refs = extractCodeRefs(content, file); + + if (refs.length > 0) { + totalRefs += refs.length; + refs.forEach((ref) => allRefs.push({ ref, file })); + + if (options.verbose) { + console.log(` ${path.relative(docsPath, file)}: ${refs.length} references`); + } + } + } + + console.log(`\n📌 Found ${totalRefs} CODE_REF references\n`); + + if (totalRefs === 0) { + console.log('✅ No CODE_REF references found (no validation needed)'); + process.exit(0); + } + + // Validate each reference + const allErrors = await Promise.all( + allRefs.map(({ ref }) => validateCodeRef(ref, config)) + ).then((results) => results.flat()); + + // Display results + if (allErrors.length === 0) { + console.log('✅ All CODE_REF references are valid!'); + process.exit(0); + } else { + console.log(`❌ Found ${allErrors.length} errors:\n`); + + // Group errors by document + const errorsByDoc: Record = {}; + + for (const error of allErrors) { + const docFile = path.relative(config.projectRoot, error.ref.docFile); + + if (!errorsByDoc[docFile]) { + errorsByDoc[docFile] = []; + } + + errorsByDoc[docFile].push(error); + } + + // Display error details + for (const [docFile, errors] of Object.entries(errorsByDoc)) { + console.log(`📄 ${docFile}:`); + + for (const error of errors) { + console.log(` ❌ ${error.type}: ${error.message}`); + + // Display line number in document + const filePath = path.relative(config.projectRoot, error.ref.docFile); + const lineInfo = error.ref.docLineNumber ? `:${error.ref.docLineNumber}` : ''; + console.log(` ${filePath}${lineInfo}: ${error.ref.fullMatch}`); + + // Display diff for CODE_LOCATION_MISMATCH in verbose mode + if (error.type === 'CODE_LOCATION_MISMATCH' && error.suggestedLines && options.verbose) { + const filePath = path.join(config.projectRoot, error.ref.refPath); + const actualCode = extractLinesFromFile( + filePath, + error.suggestedLines.start, + error.suggestedLines.end + ); + const diff = displayLineRangeDiff( + actualCode, + { start: error.ref.startLine!, end: error.ref.endLine! }, + error.suggestedLines + ); + console.log(diff); + } + } + console.log(''); + } + + process.exit(1); + } +} + +// Run CLI if this file is executed directly +if (require.main === module) { + main().catch((error) => { + console.error('Error:', error); + process.exit(1); + }); +} diff --git a/src/core/validate.ts b/src/core/validate.ts index 451aca3..9694a59 100644 --- a/src/core/validate.ts +++ b/src/core/validate.ts @@ -1,14 +1,5 @@ -#!/usr/bin/env tsx - /** - * ドキュメント内のコード参照(CODE_REF)の整合性をチェックするスクリプト - * - * 使用方法: - * tsx scripts/coderef/validate.ts # 全ファイルを検証 - * tsx scripts/coderef/validate.ts --verbose # 詳細表示 - * tsx scripts/coderef/validate.ts docs/README.md # 特定ファイルのみ検証 - * tsx scripts/coderef/validate.ts docs/backend/ # 特定ディレクトリのみ検証 - * tsx scripts/coderef/validate.ts CLAUDE.md # CLAUDEファイルも可能 + * Core validation logic for CODE_REF references in markdown documentation */ import * as fs from 'fs'; @@ -25,31 +16,13 @@ import { extractLinesFromFile, searchCodeInFile, } from '../utils/code-comparison'; -import { displayCodeDiff, displayLineRangeDiff } from '../utils/diff-display'; -import { isIgnored, loadDocsignorePatterns } from '../utils/ignore-pattern'; import { associateCodeBlocksWithRefs } from '../utils/markdown'; import type { CodeRef, CodeRefError } from '../utils/types'; -import { loadConfig, getDocsPath, getIgnoreFilePath, type CodeRefConfig } from '../config'; +import { loadConfig, type CodeRefConfig } from '../config'; // CODE_REF パターン定数 const CODE_REF_PATTERN = //g; -// コマンドライン引数のパース -interface CliOptions { - verbose: boolean; - files: string[]; -} - -function parseCliArgs(): CliOptions { - const args = process.argv.slice(2); - const verbose = args.includes('--verbose') || args.includes('-v'); - const files = args.filter((arg) => !arg.startsWith('-')); - - return { verbose, files }; -} - -const { verbose, files: targetFiles } = parseCliArgs(); - /** * ディレクトリを再帰的に走査してマークダウンファイルを取得 */ @@ -444,196 +417,3 @@ export function validateCodeRef(ref: CodeRef, config?: CodeRefConfig): CodeRefEr return errors; } -/** - * 指定されたパスがディレクトリかどうかを判定 - */ -function isDirectory(filePath: string): boolean { - try { - return fs.statSync(filePath).isDirectory(); - } catch { - return false; - } -} - -/** - * 対象ファイルを解決する - * - ファイル指定がない場合: 全ファイル - * - ファイル指定がある場合: 指定されたファイル/ディレクトリのみ - */ -function resolveTargetFiles(targets: string[], config: CodeRefConfig): string[] { - const docsPath = getDocsPath(config); - const projectRoot = config.projectRoot; - - if (targets.length === 0) { - // ファイル指定がない場合は全ファイルを対象 - return findMarkdownFiles(docsPath); - } - - const resolvedFiles = new Set(); - - for (const target of targets) { - // 相対パスを絶対パスに変換 - const absolutePath = path.isAbsolute(target) ? target : path.join(projectRoot, target); - - if (isDirectory(absolutePath)) { - // ディレクトリの場合は再帰的にマークダウンファイルを検索 - const files = findMarkdownFiles(absolutePath); - files.forEach((file) => resolvedFiles.add(file)); - } else if (fs.existsSync(absolutePath)) { - // ファイルの場合はそのまま追加 - if (absolutePath.endsWith('.md')) { - resolvedFiles.add(absolutePath); - } - } else { - console.warn(`⚠️ ファイルが見つかりません: ${target}`); - } - } - - return Array.from(resolvedFiles); -} - -export async function main(): Promise { - console.log('🔍 ドキュメント内のコード参照を検証しています...\n'); - - // 設定を読み込み - const config = loadConfig({ - targets: targetFiles.length > 0 ? targetFiles : undefined, - verbose, - }); - - // 対象ファイルを解決 - const allMarkdownFiles = resolveTargetFiles(targetFiles, config); - - if (targetFiles.length > 0 && verbose) { - console.log(`📋 指定されたファイル/ディレクトリ: ${targetFiles.join(', ')}\n`); - } - - // .docsignoreパターンを読み込み - const ignoreFilePath = getIgnoreFilePath(config); - const ignorePatterns = ignoreFilePath ? loadDocsignorePatterns(ignoreFilePath) : []; - if (verbose) { - console.log(`📋 .docsignoreから${ignorePatterns.length}個のパターンを読み込みました\n`); - } - - // .docsignoreで除外されていないファイルのみを対象とする - const markdownFiles = allMarkdownFiles.filter((file) => { - const relativePath = path.relative(config.projectRoot, file); - return !isIgnored(relativePath, ignorePatterns); - }); - - if (verbose && allMarkdownFiles.length > markdownFiles.length) { - console.log( - `📋 ${allMarkdownFiles.length - markdownFiles.length}個のファイルが.docsignoreにより除外されました\n` - ); - } - - console.log(`📄 ${markdownFiles.length} 個のマークダウンファイルを検出\n`); - - // 全てのCODE_REFを抽出 - let totalRefs = 0; - const allRefs: CodeRef[] = []; - - for (const file of markdownFiles) { - const content = fs.readFileSync(file, 'utf-8'); - const refs = extractCodeRefs(content, file); - - if (refs.length > 0) { - totalRefs += refs.length; - allRefs.push(...refs); - - if (verbose) { - console.log(` ${path.relative(getDocsPath(config), file)}: ${refs.length} 個の参照`); - } - } - } - - console.log(`\n📌 ${totalRefs} 個のコード参照を検出\n`); - - if (totalRefs === 0) { - console.log('✅ コード参照が見つかりませんでした(検証不要)'); - process.exit(0); - } - - // 各参照を検証 - const allErrors = await Promise.all(allRefs.map((ref) => validateCodeRef(ref, config))).then((results) => - results.flat() - ); - - // 結果の表示 - if (allErrors.length === 0) { - console.log('✅ 全てのコード参照が有効です!'); - process.exit(0); - } else { - console.log(`❌ ${allErrors.length} 個のエラーが見つかりました:\n`); - - // エラーをグループ化して表示 - const errorsByDoc: Record = {}; - - for (const error of allErrors) { - const docFile = path.relative(config.projectRoot, error.ref.docFile); - - if (!errorsByDoc[docFile]) { - errorsByDoc[docFile] = []; - } - - errorsByDoc[docFile].push(error); - } - - // エラー詳細の表示 - for (const [docFile, errors] of Object.entries(errorsByDoc)) { - console.log(`📄 ${docFile}:`); - - for (const error of errors) { - console.log(` ❌ ${error.type}: ${error.message}`); - - // ドキュメント内の行番号を表示 - const filePath = path.relative(config.projectRoot, error.ref.docFile); - const lineInfo = error.ref.docLineNumber ? `:${error.ref.docLineNumber}` : ''; - console.log(` ${filePath}${lineInfo}: ${error.ref.fullMatch}`); - - // CODE_LOCATION_MISMATCHの場合、行範囲の差分を表示 - if (error.type === 'CODE_LOCATION_MISMATCH' && error.suggestedLines && verbose) { - // verboseモードでは詳細な差分を表示 - const filePath = path.join(config.projectRoot, error.ref.refPath); - const actualCode = extractLinesFromFile( - filePath, - error.suggestedLines.start, - error.suggestedLines.end - ); - const diff = displayLineRangeDiff( - actualCode, - { start: error.ref.startLine!, end: error.ref.endLine! }, - error.suggestedLines - ); - console.log(diff); - } - - // CODE_CONTENT_MISMATCHの場合、差分を表示 - if ( - error.type === 'CODE_CONTENT_MISMATCH' && - verbose && - error.expectedCode && - error.actualCode - ) { - // verboseモードでは詳細な差分を表示 - const diff = displayCodeDiff(error.expectedCode, error.actualCode); - console.log(diff); - } - - console.log(''); - } - } - - console.log(`\n💡 ヒント:`); - console.log(` - ファイルパスがプロジェクトルートからの相対パスになっているか確認してください`); - console.log(` - 行番号が最新のコードと一致しているか確認してください`); - console.log(` - 詳細情報を表示するには --verbose オプションを使用してください`); - - process.exit(1); - } -} - -// スクリプトが直接実行された場合のみmainを実行 -if (require.main === module) { - main(); -} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..5f76774 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,67 @@ +/** + * @cawpea/coderef - Validate and fix code references in markdown documentation + * + * This is the main entry point for programmatic usage. + * For CLI usage, use the `coderef` command. + * + * @example + * ```typescript + * import { validateCodeRef, extractCodeRefs, loadConfig } from '@cawpea/coderef'; + * + * const config = loadConfig(); + * const refs = extractCodeRefs(markdownContent, 'docs/README.md'); + * const errors = validateCodeRef(refs[0], config); + * ``` + */ + +// Core validation functions +export { + findMarkdownFiles, + extractCodeRefs, + validateCodeRef, + validateCodeContent, + validateSymbolRef, +} from './core/validate'; + +// Configuration +export { + loadConfig, + loadFixConfig, + resolveProjectPath, + getDocsPath, + getIgnoreFilePath, + type CodeRefConfig, + type CodeRefFixConfig, + type PartialCodeRefConfig, + type PartialCodeRefFixConfig, +} from './config'; + +// Types +export type { + CodeRef, + CodeRefError, + SymbolMatch, + FixAction, + FixOptions, + FixResult, + ExpandedMatch, +} from './utils/types'; + +// Utility functions +export { + isTypeScriptOrJavaScript, + findSymbolInAST, + parseSymbolPath, + selectBestSymbolMatch, +} from './utils/ast-symbol-search'; + +export { + compareCodeContent, + extractLinesFromFile, + searchCodeInFile, +} from './utils/code-comparison'; + +export { + isIgnored, + loadDocsignorePatterns, +} from './utils/ignore-pattern'; From a681f74dd294c4e7f74c5aee8ff28ce14c3860b5 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Mon, 29 Dec 2025 22:12:35 +0900 Subject: [PATCH 09/57] test: move test files to be co-location --- .claude/settings.local.json | 13 ++++++++++- CLAUDE.md | 4 ++-- jest.config.js | 3 ++- package.json | 4 ++-- {test => src}/config.test.ts | 2 +- {test => src/core}/validate.test.ts | 6 ++--- {test => src/utils}/ast-symbol-search.test.ts | 4 ++-- {test => src/utils}/code-comparison.test.ts | 2 +- {test => src/utils}/diff-display.test.ts | 2 +- {test => src/utils}/fix.test.ts | 22 +++++++++---------- {test => src/utils}/ignore-pattern.test.ts | 2 +- {test => src/utils}/markdown.test.ts | 4 ++-- tsconfig.json | 2 +- 13 files changed, 41 insertions(+), 29 deletions(-) rename {test => src}/config.test.ts (99%) rename {test => src/core}/validate.test.ts (99%) rename {test => src/utils}/ast-symbol-search.test.ts (98%) rename {test => src/utils}/code-comparison.test.ts (99%) rename {test => src/utils}/diff-display.test.ts (99%) rename {test => src/utils}/fix.test.ts (97%) rename {test => src/utils}/ignore-pattern.test.ts (99%) rename {test => src/utils}/markdown.test.ts (99%) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 524551e..9e1f940 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -5,7 +5,18 @@ "Bash(find:*)", "Bash(cat:*)", "Bash(git ls-tree:*)", - "Bash(ls:*)" + "Bash(ls:*)", + "Bash(npm test)", + "Bash(npm run test:coverage:*)", + "Bash(git add:*)", + "Bash(wc:*)", + "Bash(npm run type-check:*)", + "Bash(npm run build:*)", + "Bash(node bin/coderef.js:*)", + "Bash(git mv:*)", + "Bash(npm test:*)", + "Bash(npm run format:check:*)", + "Bash(npm run lint)" ] } } diff --git a/CLAUDE.md b/CLAUDE.md index 750e654..cd25b78 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,7 +34,7 @@ npm run type-check # Run TypeScript compiler checks ### Single Test Execution ```bash -npx jest # Run specific test file +npx jest src/utils/foo.test.ts # Run specific test file npx jest -t "" # Run tests matching pattern ``` @@ -91,7 +91,7 @@ The tool supports three reference patterns: Uses `@typescript-eslint/typescript-estree` for TypeScript/JavaScript symbol searching and code extraction. ### Test Configuration -- Test files: `test/**/*.test.ts` +- Test files: Co-located with source files as `**/*.test.ts` - Path alias: `@/` maps to `src/` - Environment: Node.js - Preset: ts-jest diff --git a/jest.config.js b/jest.config.js index 258bc83..b14efe5 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,11 +2,12 @@ module.exports = { preset: 'ts-jest', testEnvironment: 'node', - roots: ['/test'], + roots: ['/src'], testMatch: ['**/*.test.ts'], collectCoverageFrom: [ 'src/**/*.ts', '!src/**/*.d.ts', + '!src/**/*.test.ts', '!src/cli/**/*.ts', // CLI はカバレッジから除外(統合テストで検証) ], coverageThreshold: { diff --git a/package.json b/package.json index 45a9cf7..829a24a 100644 --- a/package.json +++ b/package.json @@ -41,8 +41,8 @@ "test:coverage": "jest --coverage", "lint": "eslint src --ext .ts", "lint:fix": "eslint src --ext .ts --fix", - "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", - "format:check": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\"", + "format": "prettier --write \"src/**/*.ts\"", + "format:check": "prettier --check \"src/**/*.ts\"", "type-check": "tsc --noEmit", "prepublishOnly": "npm run build && npm test" }, diff --git a/test/config.test.ts b/src/config.test.ts similarity index 99% rename from test/config.test.ts rename to src/config.test.ts index daaba2d..034444d 100644 --- a/test/config.test.ts +++ b/src/config.test.ts @@ -7,7 +7,7 @@ import { getDocsPath, getIgnoreFilePath, type CodeRefConfig, -} from '../src/config'; +} from './config'; describe('Config System', () => { const originalCwd = process.cwd(); diff --git a/test/validate.test.ts b/src/core/validate.test.ts similarity index 99% rename from test/validate.test.ts rename to src/core/validate.test.ts index 32c220d..a77b4a6 100644 --- a/test/validate.test.ts +++ b/src/core/validate.test.ts @@ -3,9 +3,9 @@ import * as path from 'path'; import { jest } from '@jest/globals'; -import type { CodeRef } from '../src/utils/types'; -import { extractCodeRefs, findMarkdownFiles, validateCodeRef } from '../src/core/validate'; -import type { CodeRefConfig } from '../src/config'; +import type { CodeRef } from '../utils/types'; +import { extractCodeRefs, findMarkdownFiles, validateCodeRef } from './validate'; +import type { CodeRefConfig } from '../config'; // テスト用のモックプロジェクトルート const mockProjectRoot = '/project'; diff --git a/test/ast-symbol-search.test.ts b/src/utils/ast-symbol-search.test.ts similarity index 98% rename from test/ast-symbol-search.test.ts rename to src/utils/ast-symbol-search.test.ts index 1cc4f11..dd90f41 100644 --- a/test/ast-symbol-search.test.ts +++ b/src/utils/ast-symbol-search.test.ts @@ -4,8 +4,8 @@ import { isTypeScriptOrJavaScript, parseSymbolPath, selectBestSymbolMatch, -} from '../src/utils/ast-symbol-search'; -import type { SymbolMatch } from '../src/utils/types'; +} from './ast-symbol-search'; +import type { SymbolMatch } from './types'; describe('ast-symbol-search', () => { afterEach(() => { diff --git a/test/code-comparison.test.ts b/src/utils/code-comparison.test.ts similarity index 99% rename from test/code-comparison.test.ts rename to src/utils/code-comparison.test.ts index 3d2629c..7c717df 100644 --- a/test/code-comparison.test.ts +++ b/src/utils/code-comparison.test.ts @@ -7,7 +7,7 @@ import { dedentCode, extractLinesFromFile, searchCodeInFile, -} from '../src/utils/code-comparison'; +} from './code-comparison'; // fsモジュールをモック jest.mock('fs'); diff --git a/test/diff-display.test.ts b/src/utils/diff-display.test.ts similarity index 99% rename from test/diff-display.test.ts rename to src/utils/diff-display.test.ts index 0ff0b57..9f86be6 100644 --- a/test/diff-display.test.ts +++ b/src/utils/diff-display.test.ts @@ -1,4 +1,4 @@ -import { displayCodeDiff, displayLineRangeDiff, truncateText } from '../src/utils/diff-display'; +import { displayCodeDiff, displayLineRangeDiff, truncateText } from './diff-display'; describe('displayCodeDiff', () => { it('一致するコードの場合、差分を表示しない', () => { diff --git a/test/fix.test.ts b/src/utils/fix.test.ts similarity index 97% rename from test/fix.test.ts rename to src/utils/fix.test.ts index f22f2c3..a539f58 100644 --- a/test/fix.test.ts +++ b/src/utils/fix.test.ts @@ -5,7 +5,7 @@ import * as fs from 'fs'; import * as readline from 'readline'; -import { extractLinesFromFile, searchCodeInFileWithScopeExpansion } from '../src/utils/code-comparison'; +import { extractLinesFromFile, searchCodeInFileWithScopeExpansion } from './code-comparison'; import { applyFix, createBlockMissingFix, @@ -17,21 +17,21 @@ import { createSymbolRangeMismatchFix, handleMultipleMatches, isFixableError, -} from '../src/utils/fix'; -import * as markdownEdit from '../src/utils/markdown-edit'; -import * as prompt from '../src/utils/prompt'; -import { CodeRefError, FixAction } from '../src/utils/types'; +} from './fix'; +import * as markdownEdit from './markdown-edit'; +import * as prompt from './prompt'; +import { CodeRefError, FixAction } from './types'; // モック設定 jest.mock('fs'); -jest.mock('../src/utils/code-comparison', () => ({ +jest.mock('./code-comparison', () => ({ searchCodeInFile: jest.fn(), searchCodeInFileWithScopeExpansion: jest.fn(), extractLinesFromFile: jest.fn(), })); -jest.mock('../src/utils/markdown-edit'); -jest.mock('../src/utils/prompt'); -jest.mock('../src/utils/ast-scope-expansion'); +jest.mock('./markdown-edit'); +jest.mock('./prompt'); +jest.mock('./ast-scope-expansion'); const mockFs = fs as jest.Mocked; const mockExtractLinesFromFile = extractLinesFromFile as jest.MockedFunction< @@ -215,7 +215,7 @@ describe('createContentMismatchFix', () => { mockFs.readFileSync.mockReturnValue('file content' as any); // ast-scope-expansionモックで空配列を返す(拡張なし) - const astScopeExpansion = require('../src/utils/ast-scope-expansion'); // eslint-disable-line + const astScopeExpansion = require('./ast-scope-expansion'); // eslint-disable-line astScopeExpansion.expandMatchToScope = jest.fn().mockReturnValue([]); const result = createContentMismatchFix(error) as FixAction; @@ -243,7 +243,7 @@ describe('createContentMismatchFix', () => { mockFs.readFileSync.mockReturnValue('file content' as any); // ast-scope-expansionモックで拡張されたマッチを返す - const astScopeExpansion = require('../src/utils/ast-scope-expansion'); // eslint-disable-line + const astScopeExpansion = require('./ast-scope-expansion'); // eslint-disable-line astScopeExpansion.expandMatchToScope = jest.fn().mockReturnValue([ { start: 8, diff --git a/test/ignore-pattern.test.ts b/src/utils/ignore-pattern.test.ts similarity index 99% rename from test/ignore-pattern.test.ts rename to src/utils/ignore-pattern.test.ts index e1a2817..da238c0 100644 --- a/test/ignore-pattern.test.ts +++ b/src/utils/ignore-pattern.test.ts @@ -1,6 +1,6 @@ import * as fs from 'fs'; -import { isIgnored, loadDocsignorePatterns, matchesPattern } from '../src/utils/ignore-pattern'; +import { isIgnored, loadDocsignorePatterns, matchesPattern } from './ignore-pattern'; // fsをモック jest.mock('fs'); diff --git a/test/markdown.test.ts b/src/utils/markdown.test.ts similarity index 99% rename from test/markdown.test.ts rename to src/utils/markdown.test.ts index da7681b..0b286c2 100644 --- a/test/markdown.test.ts +++ b/src/utils/markdown.test.ts @@ -4,8 +4,8 @@ import { associateCodeBlocksWithRefs, extractCodeBlockAfterComment, normalizeCode, -} from '../src/utils/markdown'; -import type { CodeRef } from '../src/utils/types'; +} from './markdown'; +import type { CodeRef } from './types'; describe('markdown.utils', () => { describe('extractCodeBlockAfterComment', () => { diff --git a/tsconfig.json b/tsconfig.json index 1fc5d06..05baa34 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,5 +22,5 @@ "sourceMap": true }, "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "test"] + "exclude": ["node_modules", "dist", "**/*.test.ts"] } From ccf41aa17755bdfb5a0326047479df3080525341 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Mon, 29 Dec 2025 23:25:56 +0900 Subject: [PATCH 10/57] style: add ESLint and Prettier --- .claude/settings.local.json | 4 +- .gitignore | 4 + .husky/pre-commit | 1 + .prettierignore | 25 ++ CLAUDE.md | 71 ++++- eslint.config.js | 104 +++++++ package-lock.json | 477 ++++++++++++++++++++++++++++++- package.json | 21 +- prettier.config.js | 34 +++ src/cli/fix.ts | 2 +- src/cli/validate.ts | 8 +- src/config.test.ts | 10 +- src/config.ts | 12 +- src/core/validate.test.ts | 6 +- src/core/validate.ts | 10 +- src/index.ts | 5 +- src/utils/ast-scope-expansion.ts | 6 +- src/utils/ast-symbol-search.ts | 16 +- src/utils/code-comparison.ts | 2 +- src/utils/code-ellipsis.ts | 8 +- src/utils/diff-display.ts | 2 +- src/utils/fix.test.ts | 8 +- src/utils/fix.ts | 15 +- src/utils/ignore-pattern.ts | 8 +- src/utils/markdown-edit.ts | 8 +- src/utils/markdown.ts | 4 +- src/utils/types.ts | 2 +- 27 files changed, 776 insertions(+), 97 deletions(-) create mode 100755 .husky/pre-commit create mode 100644 .prettierignore create mode 100644 eslint.config.js create mode 100644 prettier.config.js diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 9e1f940..bc911d7 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -16,7 +16,9 @@ "Bash(git mv:*)", "Bash(npm test:*)", "Bash(npm run format:check:*)", - "Bash(npm run lint)" + "Bash(npm run lint)", + "Bash(npm run format:*)", + "Bash(npm run lint:fix:*)" ] } } diff --git a/.gitignore b/.gitignore index 9a5aced..d863396 100644 --- a/.gitignore +++ b/.gitignore @@ -123,8 +123,12 @@ dist .tern-port # Stores VSCode versions used for testing VSCode extensions +.vscode/ .vscode-test +# Claude.ai files +.claude/ + # yarn v3 .pnp.* .yarn/* diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..2312dc5 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..44c39a5 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,25 @@ +# Build outputs +dist/ +build/ +coverage/ + +# Dependencies +node_modules/ + +# Lock files (machine-generated) +package-lock.json +yarn.lock +pnpm-lock.yaml + +# Logs +*.log + +# Cache +.eslintcache +*.tsbuildinfo + +# Test snapshots (if any) +**/__snapshots__/ + +# Generated files +CHANGELOG.md diff --git a/CLAUDE.md b/CLAUDE.md index cd25b78..7898bb8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,12 +9,14 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Development Commands ### Building + ```bash npm run build # Build for production (CJS + ESM + types) npm run dev # Watch mode for development ``` ### Testing + ```bash npm test # Run all tests npm run test:watch # Run tests in watch mode @@ -24,6 +26,7 @@ npm run test:coverage # Run tests with coverage report Coverage thresholds are set to 80% for all metrics (branches, functions, lines, statements). CLI code is excluded from coverage as it's verified through integration tests. ### Linting & Formatting + ```bash npm run lint # Lint TypeScript files npm run lint:fix # Auto-fix linting issues @@ -32,7 +35,40 @@ npm run format:check # Check formatting without modifying npm run type-check # Run TypeScript compiler checks ``` +#### Pre-commit Hooks + +This project uses [husky](https://typicode.github.io/husky/) and [lint-staged](https://github.com/okonet/lint-staged) to automatically lint and format staged files before each commit. + +The pre-commit hook will: + +1. Run ESLint with auto-fix on staged TypeScript files +2. Run Prettier on staged files (TS, JS, JSON, MD) +3. Prevent commit if linting errors remain + +To skip hooks (not recommended): + +```bash +git commit --no-verify +``` + +#### Configuration Files + +- `prettier.config.js` - Code formatting rules +- `eslint.config.js` - Linting rules (ESLint 9 flat config) +- `.vscode/settings.json` - VS Code auto-format on save + +#### Code Style + +- **Indentation**: 2 spaces +- **Semicolons**: Enabled +- **Quotes**: Single quotes +- **Print width**: 100 characters +- **Line endings**: LF (Unix) +- **Trailing commas**: ES5 style +- **Type imports**: Prefer `import type` for type-only imports + ### Single Test Execution + ```bash npx jest src/utils/foo.test.ts # Run specific test file npx jest -t "" # Run tests matching pattern @@ -43,6 +79,7 @@ npx jest -t "" # Run tests matching pattern This project follows [Conventional Commits](https://www.conventionalcommits.org/) specification for commit messages. ### Commit Message Format + ``` : @@ -53,34 +90,38 @@ This project follows [Conventional Commits](https://www.conventionalcommits.org/ ### Commit Types -| Type | Description | Semantic Version Impact | Examples | -| ---------- | -------------------------------------------------------------- | ----------------------- | ------------------------------------------ | -| `feat` | New feature | MINOR (1.x.0) | Add evaluation agent, new UI component | -| `fix` | Bug fix | PATCH (1.0.x) | Fix contrast ratio calculation error | -| `docs` | Documentation only changes | None | Update README, add comments | -| `style` | Changes that don't affect code meaning (whitespace, formatting)| None | Run Prettier, fix indentation | -| `refactor` | Code changes that neither fix bugs nor add features | None | Split function, rename variables | -| `test` | Adding or updating tests | None | Add unit tests, improve mocks | -| `chore` | Changes to build process or tools | None | Update dependencies, modify config files | -| `ci` | Changes to CI configuration files and scripts | None | Update GitHub Actions | -| `perf` | Performance improvements | PATCH (1.0.x) | Optimize API response time | -| `build` | Changes to build system or external dependencies | None | Modify Webpack config, add npm scripts | -| `revert` | Revert a previous commit | Depends on original | Revert previous commit | +| Type | Description | Semantic Version Impact | Examples | +| ---------- | --------------------------------------------------------------- | ----------------------- | ---------------------------------------- | +| `feat` | New feature | MINOR (1.x.0) | Add evaluation agent, new UI component | +| `fix` | Bug fix | PATCH (1.0.x) | Fix contrast ratio calculation error | +| `docs` | Documentation only changes | None | Update README, add comments | +| `style` | Changes that don't affect code meaning (whitespace, formatting) | None | Run Prettier, fix indentation | +| `refactor` | Code changes that neither fix bugs nor add features | None | Split function, rename variables | +| `test` | Adding or updating tests | None | Add unit tests, improve mocks | +| `chore` | Changes to build process or tools | None | Update dependencies, modify config files | +| `ci` | Changes to CI configuration files and scripts | None | Update GitHub Actions | +| `perf` | Performance improvements | PATCH (1.0.x) | Optimize API response time | +| `build` | Changes to build system or external dependencies | None | Modify Webpack config, add npm scripts | +| `revert` | Revert a previous commit | Depends on original | Revert previous commit | ## Architecture ### Build System + - **tsup**: Generates both CJS and ESM formats with type declarations - Output directory: `./dist` - Entry points defined in package.json exports for proper dual-package support ### Module Structure + The project is organized into three main directories under `src/`: + - `cli/`: Command-line interface implementations (validate.ts, fix.ts) - `core/`: Core validation and fixing logic - `utils/`: Shared utility functions ### CODE_REF Syntax Patterns + The tool supports three reference patterns: 1. **Line-based references**: `` @@ -88,16 +129,20 @@ The tool supports three reference patterns: 3. **Class method references**: `` ### AST Parsing + Uses `@typescript-eslint/typescript-estree` for TypeScript/JavaScript symbol searching and code extraction. ### Test Configuration + - Test files: Co-located with source files as `**/*.test.ts` - Path alias: `@/` maps to `src/` - Environment: Node.js - Preset: ts-jest ## Publishing + The `prepublishOnly` script ensures both build and tests pass before publishing to npm. ## Node Version + Minimum Node.js version: 16.0.0 diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..184c862 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,104 @@ +// @ts-check +const eslint = require('@eslint/js'); +const tseslint = require('typescript-eslint'); +const prettierConfig = require('eslint-config-prettier'); + +module.exports = tseslint.config( + // Base recommended configs + eslint.configs.recommended, + + // Global ignores + { + ignores: [ + 'dist/**', + 'node_modules/**', + 'coverage/**', + '*.js', // Ignore JS files at root (configs) + 'bin/**/*.js', // CLI wrapper is plain JS + 'jest.config.js', + ], + }, + + // TypeScript files configuration (non-test files with type checking) + { + files: ['src/**/*.ts'], + ignores: ['src/**/*.test.ts'], // Exclude test files from type checking + extends: [...tseslint.configs.recommendedTypeChecked, ...tseslint.configs.stylisticTypeChecked], + languageOptions: { + parser: tseslint.parser, + parserOptions: { + project: './tsconfig.json', + tsconfigRootDir: __dirname, + }, + }, + plugins: { + '@typescript-eslint': tseslint.plugin, + }, + rules: { + // TypeScript-specific rules + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + }, + ], + '@typescript-eslint/no-non-null-assertion': 'warn', + '@typescript-eslint/consistent-type-imports': [ + 'error', + { + prefer: 'type-imports', + }, + ], + + // Relax strict type-checking rules for existing codebase + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/no-unsafe-return': 'off', + '@typescript-eslint/no-unsafe-enum-comparison': 'off', + '@typescript-eslint/prefer-nullish-coalescing': 'warn', + '@typescript-eslint/prefer-optional-chain': 'warn', + '@typescript-eslint/await-thenable': 'warn', + + // General code quality + 'no-console': 'off', // CLI tool needs console + 'no-debugger': 'error', + 'no-alert': 'error', + 'prefer-const': 'error', + 'no-var': 'error', + eqeqeq: ['error', 'always', { null: 'ignore' }], + curly: ['error', 'all'], + + // Code style (supplementing Prettier) + 'prefer-arrow-callback': 'error', + 'prefer-template': 'error', + 'object-shorthand': ['error', 'always'], + 'no-useless-concat': 'error', + }, + }, + + // Test files configuration (basic linting without type checking) + { + files: ['src/**/*.test.ts'], + extends: [...tseslint.configs.recommended], + languageOptions: { + parser: tseslint.parser, + }, + plugins: { + '@typescript-eslint': tseslint.plugin, + }, + rules: { + '@typescript-eslint/no-empty-function': 'off', // Test mocks often use empty functions + '@typescript-eslint/no-explicit-any': 'off', // Tests can use any for simplicity + }, + }, + + // Prettier integration (must be last to disable conflicting rules) + prettierConfig +); diff --git a/package-lock.json b/package-lock.json index 8d23074..5903b47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,11 +22,14 @@ "@typescript-eslint/parser": "^8.18.1", "eslint": "^9.17.0", "eslint-config-prettier": "^9.1.0", + "husky": "^9.1.7", "jest": "^30.2.0", + "lint-staged": "^16.2.7", "prettier": "^3.4.2", "ts-jest": "^29.4.5", "tsup": "^8.0.0", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "typescript-eslint": "^8.50.1" }, "engines": { "node": ">=16.0.0" @@ -3256,6 +3259,56 @@ "dev": true, "license": "MIT" }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", + "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^7.1.0", + "string-width": "^8.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -3372,6 +3425,13 @@ "dev": true, "license": "MIT" }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, "node_modules/commander": { "version": "14.0.2", "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", @@ -3520,6 +3580,19 @@ "dev": true, "license": "MIT" }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/error-ex": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", @@ -3836,6 +3909,13 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true, + "license": "MIT" + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -4061,6 +4141,19 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -4187,6 +4280,22 @@ "node": ">=10.17.0" } }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/ignore": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", @@ -5161,6 +5270,105 @@ "dev": true, "license": "MIT" }, + "node_modules/lint-staged": { + "version": "16.2.7", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.7.tgz", + "integrity": "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^14.0.2", + "listr2": "^9.0.5", + "micromatch": "^4.0.8", + "nano-spawn": "^2.0.0", + "pidtree": "^0.6.0", + "string-argv": "^0.3.2", + "yaml": "^2.8.1" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/listr2": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^5.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/listr2/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/load-tsconfig": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", @@ -5201,6 +5409,98 @@ "dev": true, "license": "MIT" }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-escapes": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", + "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -5285,6 +5585,19 @@ "node": ">=6" } }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -5351,6 +5664,19 @@ "thenify-all": "^1.0.0" } }, + "node_modules/nano-spawn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz", + "integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1" + } + }, "node_modules/napi-postinstall": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", @@ -5634,6 +5960,19 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/pirates": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", @@ -5913,6 +6252,46 @@ "node": ">=4" } }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, "node_modules/rollup": { "version": "4.54.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", @@ -6013,6 +6392,52 @@ "node": ">=8" } }, + "node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -6064,6 +6489,16 @@ "node": ">=8" } }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -6681,6 +7116,30 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.50.1.tgz", + "integrity": "sha512-ytTHO+SoYSbhAH9CrYnMhiLx8To6PSSvqnvXyPUgPETCvB6eBKmTI9w6XMPS3HsBRGkwTVBX+urA8dYQx6bHfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.50.1", + "@typescript-eslint/parser": "8.50.1", + "@typescript-eslint/typescript-estree": "8.50.1", + "@typescript-eslint/utils": "8.50.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, "node_modules/ufo": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", @@ -6976,6 +7435,22 @@ "dev": true, "license": "ISC" }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index 829a24a..16162a8 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "CHANGELOG.md" ], "scripts": { + "prepare": "husky install", "build": "tsup src/index.ts src/cli/validate.ts src/cli/fix.ts --format cjs,esm --dts --clean", "dev": "tsup src/index.ts --format cjs,esm --dts --watch", "test": "jest", @@ -41,8 +42,8 @@ "test:coverage": "jest --coverage", "lint": "eslint src --ext .ts", "lint:fix": "eslint src --ext .ts --fix", - "format": "prettier --write \"src/**/*.ts\"", - "format:check": "prettier --check \"src/**/*.ts\"", + "format": "prettier --write \"src/**/*.ts\" \"*.{js,json,md}\"", + "format:check": "prettier --check \"src/**/*.ts\" \"*.{js,json,md}\"", "type-check": "tsc --noEmit", "prepublishOnly": "npm run build && npm test" }, @@ -60,11 +61,14 @@ "@typescript-eslint/parser": "^8.18.1", "eslint": "^9.17.0", "eslint-config-prettier": "^9.1.0", + "husky": "^9.1.7", "jest": "^30.2.0", + "lint-staged": "^16.2.7", "prettier": "^3.4.2", "ts-jest": "^29.4.5", "tsup": "^8.0.0", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "typescript-eslint": "^8.50.1" }, "repository": { "type": "git", @@ -75,5 +79,14 @@ }, "homepage": "https://github.com/cawpea/coderef#readme", "author": "cawpea", - "license": "MIT" + "license": "MIT", + "lint-staged": { + "*.ts": [ + "eslint --fix", + "prettier --write" + ], + "*.{js,json,md}": [ + "prettier --write" + ] + } } diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..e6e44ca --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,34 @@ +/** @type {import("prettier").Config} */ +module.exports = { + // Match existing code style + semi: true, + singleQuote: true, + trailingComma: 'es5', + tabWidth: 2, + useTabs: false, + printWidth: 100, + endOfLine: 'lf', + + // Spacing preferences + arrowParens: 'always', + bracketSpacing: true, + bracketSameLine: false, + + // Override for specific file types + overrides: [ + { + files: '*.md', + options: { + printWidth: 80, + proseWrap: 'preserve', + }, + }, + { + files: '*.json', + options: { + printWidth: 80, + tabWidth: 2, + }, + }, + ], +}; diff --git a/src/cli/fix.ts b/src/cli/fix.ts index aa30cf5..666829d 100644 --- a/src/cli/fix.ts +++ b/src/cli/fix.ts @@ -244,7 +244,7 @@ async function main(): Promise { } // 結果サマリー - console.log('\n' + '='.repeat(60)); + console.log(`\n${'='.repeat(60)}`); console.log('📊 修正結果サマリー\n'); const successful = fixResults.filter((r) => r.success).length; diff --git a/src/cli/validate.ts b/src/cli/validate.ts index 427638f..e98194a 100644 --- a/src/cli/validate.ts +++ b/src/cli/validate.ts @@ -126,7 +126,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise = []; + const allRefs: { ref: any; file: string }[] = []; for (const file of markdownFiles) { const content = fs.readFileSync(file, 'utf-8'); @@ -150,9 +150,9 @@ export async function main(args: string[] = process.argv.slice(2)): Promise validateCodeRef(ref, config)) - ).then((results) => results.flat()); + const allErrors = await Promise.all(allRefs.map(({ ref }) => validateCodeRef(ref, config))).then( + (results) => results.flat() + ); // Display results if (allErrors.length === 0) { diff --git a/src/config.test.ts b/src/config.test.ts index 034444d..081e2ba 100644 --- a/src/config.test.ts +++ b/src/config.test.ts @@ -88,10 +88,7 @@ describe('Config System', () => { }, }; - fs.writeFileSync( - path.join(testDir, 'package.json'), - JSON.stringify(packageJson, null, 2) - ); + fs.writeFileSync(path.join(testDir, 'package.json'), JSON.stringify(packageJson, null, 2)); const config = loadConfig(); @@ -105,10 +102,7 @@ describe('Config System', () => { version: '1.0.0', }; - fs.writeFileSync( - path.join(testDir, 'package.json'), - JSON.stringify(packageJson, null, 2) - ); + fs.writeFileSync(path.join(testDir, 'package.json'), JSON.stringify(packageJson, null, 2)); const config = loadConfig(); diff --git a/src/config.ts b/src/config.ts index 91bbbb9..01acfeb 100644 --- a/src/config.ts +++ b/src/config.ts @@ -119,7 +119,7 @@ function loadPackageJsonConfig(projectRoot: string): PartialCodeRefConfig | null const content = fs.readFileSync(packageJsonPath, 'utf-8'); const packageJson = JSON.parse(content); return packageJson.coderef || null; - } catch (error) { + } catch (_error) { // Silently ignore package.json parsing errors return null; } @@ -162,7 +162,7 @@ function loadEnvConfig(): PartialCodeRefConfig { */ function mergeConfigs( defaultConfig: CodeRefConfig, - ...configs: Array + ...configs: (PartialCodeRefConfig | null)[] ): CodeRefConfig { const merged: Partial = {}; @@ -222,13 +222,7 @@ export function loadConfig(options: PartialCodeRefConfig = {}): CodeRefConfig { const envConfig = loadEnvConfig(); // Merge with proper precedence - const config = mergeConfigs( - defaultConfig, - packageJsonConfig, - fileConfig, - envConfig, - options - ); + const config = mergeConfigs(defaultConfig, packageJsonConfig, fileConfig, envConfig, options); // Validate final configuration validateConfig(config); diff --git a/src/core/validate.test.ts b/src/core/validate.test.ts index a77b4a6..9f180df 100644 --- a/src/core/validate.test.ts +++ b/src/core/validate.test.ts @@ -39,7 +39,7 @@ jest.mock('path', () => { // 親ディレクトリに移動 const parts = acc.split('/').filter(Boolean); parts.pop(); - return '/' + parts.join('/'); + return `/${parts.join('/')}`; } else if (arg === '.') { // 現在のディレクトリ return acc; @@ -51,9 +51,9 @@ jest.mock('path', () => { }, ''); } // デフォルト: 全ての引数を結合 - return '/' + args.filter((arg) => arg && arg !== '.').join('/'); + return `/${args.filter((arg) => arg && arg !== '.').join('/')}`; }), - relative: jest.fn((from: string, to: string) => to.replace(from + '/', '')), + relative: jest.fn((from: string, to: string) => to.replace(`${from}/`, '')), }; }); diff --git a/src/core/validate.ts b/src/core/validate.ts index 9694a59..5e25b46 100644 --- a/src/core/validate.ts +++ b/src/core/validate.ts @@ -50,8 +50,8 @@ export function findMarkdownFiles(dir: string): string[] { /** * コードブロックとインラインコードの範囲を検出 */ -function getCodeBlockRanges(content: string): Array<{ start: number; end: number }> { - const ranges: Array<{ start: number; end: number }> = []; +function getCodeBlockRanges(content: string): { start: number; end: number }[] { + const ranges: { start: number; end: number }[] = []; // トリプルバッククォートのコードブロック const codeBlockPattern = /```[\s\S]*?```/g; @@ -79,10 +79,7 @@ function getCodeBlockRanges(content: string): Array<{ start: number; end: number /** * 位置がコードブロックまたはインラインコード内かチェック */ -function isInsideCodeBlock( - position: number, - ranges: Array<{ start: number; end: number }> -): boolean { +function isInsideCodeBlock(position: number, ranges: { start: number; end: number }[]): boolean { return ranges.some((range) => position >= range.start && position < range.end); } @@ -416,4 +413,3 @@ export function validateCodeRef(ref: CodeRef, config?: CodeRefConfig): CodeRefEr return errors; } - diff --git a/src/index.ts b/src/index.ts index 5f76774..9a0d9fe 100644 --- a/src/index.ts +++ b/src/index.ts @@ -61,7 +61,4 @@ export { searchCodeInFile, } from './utils/code-comparison'; -export { - isIgnored, - loadDocsignorePatterns, -} from './utils/ignore-pattern'; +export { isIgnored, loadDocsignorePatterns } from './utils/ignore-pattern'; diff --git a/src/utils/ast-scope-expansion.ts b/src/utils/ast-scope-expansion.ts index c95c55d..6ea7d0d 100644 --- a/src/utils/ast-scope-expansion.ts +++ b/src/utils/ast-scope-expansion.ts @@ -52,7 +52,7 @@ function findNodeAtLine(ast: TSESTree.Program, targetLine: number): TSESTree.Nod foundNode = node; // 子ノードを探索(より具体的なノードを見つけるため) - const keys = Object.keys(node) as Array; + const keys = Object.keys(node) as (keyof TSESTree.Node)[]; for (const key of keys) { const value = node[key]; if (value && typeof value === 'object') { @@ -137,7 +137,7 @@ function findParentScope(ast: TSESTree.Program, targetNode: TSESTree.Node): TSES } // 子ノードを探索 - const keys = Object.keys(node) as Array; + const keys = Object.keys(node) as (keyof TSESTree.Node)[]; for (const key of keys) { const value = node[key]; if (value && typeof value === 'object') { @@ -232,7 +232,7 @@ function tryASTExpansion( // 親スコープを探索 const parentScope = findParentScope(ast, targetNode); - if (!parentScope || !parentScope.loc) { + if (!parentScope?.loc) { // 親スコープが見つからない場合、元のマッチを返す return { success: true, diff --git a/src/utils/ast-symbol-search.ts b/src/utils/ast-symbol-search.ts index da2bc93..1f28087 100644 --- a/src/utils/ast-symbol-search.ts +++ b/src/utils/ast-symbol-search.ts @@ -48,7 +48,7 @@ function getStartLineWithJSDoc(node: TSESTree.Node, fileContent: string): number if (!node.loc) return 1; const lines = fileContent.split('\n'); - let startLine = node.loc.start.line; + const startLine = node.loc.start.line; // ノードの直前の行から上に向かってJSDocを探す for (let i = startLine - 2; i >= 0; i--) { @@ -82,12 +82,12 @@ function findClassByName(ast: TSESTree.Program, className: string): TSESTree.Cla const classes: TSESTree.ClassDeclaration[] = []; function visit(node: TSESTree.Node) { - if (node.type === 'ClassDeclaration' && node.id && node.id.name === className) { + if (node.type === 'ClassDeclaration' && node.id?.name === className) { classes.push(node); } // 子ノードを再帰的に探索 - const keys = Object.keys(node) as Array; + const keys = Object.keys(node) as (keyof TSESTree.Node)[]; for (const key of keys) { const value = node[key]; if (value && typeof value === 'object') { @@ -115,7 +115,7 @@ function findMethodInClass( classNode: TSESTree.ClassDeclaration, methodName: string ): TSESTree.MethodDefinition | null { - if (!classNode.body || !classNode.body.body) return null; + if (!classNode.body?.body) return null; for (const member of classNode.body.body) { if (member.type === 'MethodDefinition' && member.key.type === 'Identifier') { @@ -139,15 +139,11 @@ function findFunctionByName( // トップレベルの関数のみ検索 for (const statement of ast.body) { - if ( - statement.type === 'FunctionDeclaration' && - statement.id && - statement.id.name === functionName - ) { + if (statement.type === 'FunctionDeclaration' && statement.id?.name === functionName) { functions.push(statement); } else if (statement.type === 'ExportNamedDeclaration' && statement.declaration) { const decl = statement.declaration; - if (decl.type === 'FunctionDeclaration' && decl.id && decl.id.name === functionName) { + if (decl.type === 'FunctionDeclaration' && decl.id?.name === functionName) { functions.push(decl); } } diff --git a/src/utils/code-comparison.ts b/src/utils/code-comparison.ts index 9dd6803..aa3814c 100644 --- a/src/utils/code-comparison.ts +++ b/src/utils/code-comparison.ts @@ -22,7 +22,7 @@ export function dedentCode(code: string): string { for (const line of lines) { if (line.trim().length === 0) continue; // 空行はスキップ - const indent = line.match(/^(\s*)/)?.[1].length ?? 0; + const indent = /^(\s*)/.exec(line)?.[1].length ?? 0; minIndent = Math.min(minIndent, indent); } diff --git a/src/utils/code-ellipsis.ts b/src/utils/code-ellipsis.ts index 53262d8..693bad5 100644 --- a/src/utils/code-ellipsis.ts +++ b/src/utils/code-ellipsis.ts @@ -32,12 +32,12 @@ function findClassNode(fileContent: string, className: string): TSESTree.ClassDe }); function visit(node: TSESTree.Node): TSESTree.ClassDeclaration | null { - if (node.type === 'ClassDeclaration' && node.id && node.id.name === className) { + if (node.type === 'ClassDeclaration' && node.id?.name === className) { return node; } // 子ノードを再帰的に探索 - const keys = Object.keys(node) as Array; + const keys = Object.keys(node) as (keyof TSESTree.Node)[]; for (const key of keys) { const value = node[key]; if (value && typeof value === 'object') { @@ -67,7 +67,7 @@ function findClassNode(fileContent: string, className: string): TSESTree.ClassDe function getClassMembers(classNode: TSESTree.ClassDeclaration): ClassMember[] { const members: ClassMember[] = []; - if (!classNode.body || !classNode.body.body) { + if (!classNode.body?.body) { return members; } @@ -153,7 +153,7 @@ export function insertEllipsis( if (options.className) { // クラス内のメソッドのみ表示 const classNode = findClassNode(fileContent, options.className); - if (!classNode || !classNode.loc) { + if (!classNode?.loc) { // クラスが見つからない場合は元のファイル内容を返す return fileContent; } diff --git a/src/utils/diff-display.ts b/src/utils/diff-display.ts index fc4ba6c..816ffd2 100644 --- a/src/utils/diff-display.ts +++ b/src/utils/diff-display.ts @@ -121,5 +121,5 @@ export function truncateText(text: string, maxLength: number): string { if (text.length <= maxLength) { return text; } - return text.substring(0, maxLength) + '...'; + return `${text.substring(0, maxLength)}...`; } diff --git a/src/utils/fix.test.ts b/src/utils/fix.test.ts index a539f58..e10be95 100644 --- a/src/utils/fix.test.ts +++ b/src/utils/fix.test.ts @@ -3,7 +3,7 @@ */ import * as fs from 'fs'; -import * as readline from 'readline'; +import type * as readline from 'readline'; import { extractLinesFromFile, searchCodeInFileWithScopeExpansion } from './code-comparison'; import { @@ -20,7 +20,7 @@ import { } from './fix'; import * as markdownEdit from './markdown-edit'; import * as prompt from './prompt'; -import { CodeRefError, FixAction } from './types'; +import type { CodeRefError, FixAction } from './types'; // モック設定 jest.mock('fs'); @@ -488,7 +488,7 @@ describe('優先順位付けロジック', () => { const createMockError = ( startLine: number, endLine: number, - codeBlock: string = 'test code' + codeBlock = 'test code' ): CodeRefError => ({ type: 'CODE_LOCATION_MISMATCH', message: 'Test error', @@ -502,7 +502,7 @@ describe('優先順位付けロジック', () => { }, }); - const createMockReadline = (answer: number = 0): readline.Interface => { + const createMockReadline = (answer = 0): readline.Interface => { const rl = { question: jest.fn((_query, callback) => { // ユーザーが選択肢を選んだとして、すぐにコールバックを呼ぶ diff --git a/src/utils/fix.ts b/src/utils/fix.ts index d5b7c3b..d344829 100644 --- a/src/utils/fix.ts +++ b/src/utils/fix.ts @@ -4,7 +4,7 @@ import * as fs from 'fs'; import * as path from 'path'; -import * as readline from 'readline'; +import type * as readline from 'readline'; import { expandMatchToScope } from './ast-scope-expansion'; import { findSymbolInAST } from './ast-symbol-search'; @@ -86,17 +86,17 @@ function findCodeBlockNearComment( const searchWindow = content.substring(searchStart, searchStart + 5000); // 次のCODE_REFコメントを検索 - const nextCommentMatch = searchWindow.match(/ + +Some text`; + + const result = replaceCodeRefComment( + content, + '', + '' + ); + + expect(result).toContain(''); + expect(result).not.toContain(''); + }); + + it('CODE_REFコメントが見つからない場合、エラーをスローすること', () => { + const content = `# Title + +Some text`; + + expect(() => { + replaceCodeRefComment( + content, + '', + '' + ); + }).toThrow('CODE_REFコメントが見つかりません'); + }); + + it('複数のCODE_REFコメントがある場合、最初のマッチを置換すること', () => { + const content = ` + +`; + + const result = replaceCodeRefComment( + content, + '', + '' + ); + + expect(result).toContain(''); + expect(result).toContain(''); + }); + }); + + describe('insertCodeBlockAfterComment', () => { + it('CODE_REFコメントの後にコードブロックを挿入すること', () => { + const content = `# Title + + + +Some text`; + + const result = insertCodeBlockAfterComment( + content, + '', + 'const x = 1;' + ); + + expect(result).toContain(''); + expect(result).toContain('```typescript'); + expect(result).toContain('const x = 1;'); + expect(result).toContain('```'); + }); + + it('言語指定をカスタマイズできること', () => { + const content = ``; + + const result = insertCodeBlockAfterComment( + content, + '', + 'const x = 1;', + 'javascript' + ); + + expect(result).toContain('```javascript'); + expect(result).toContain('const x = 1;'); + }); + + it('CODE_REFコメントが見つからない場合、エラーをスローすること', () => { + const content = `# Title`; + + expect(() => { + insertCodeBlockAfterComment( + content, + '', + 'const x = 1;' + ); + }).toThrow('CODE_REFコメントが見つかりません'); + }); + + it('CODE_REFコメントの終了タグがない場合、エラーをスローすること', () => { + const content = ` +Some text`; + + const result = insertCodeBlockAfterComment( + content, + '', + 'const x = 1;' + ); + + expect(result).toMatch(/-->\n```typescript\nconst x = 1;\n```\n\nSome text/); + }); + }); + + describe('replaceCodeBlock', () => { + it('コードブロックを置換すること', () => { + const content = `# Title + +\`\`\`typescript +const x = 1; +\`\`\` + +Some text`; + + const result = replaceCodeBlock(content, 'const x = 1;', 'const y = 2;'); + + expect(result).toContain('const y = 2;'); + expect(result).not.toContain('const x = 1;'); + expect(result).toContain('```typescript'); + }); + + it('正規化して比較し、コードブロックを置換すること', () => { + const content = `\`\`\`typescript + const x = 1; +\`\`\``; + + const result = replaceCodeBlock(content, 'const x = 1;', 'const y = 2;'); + + expect(result).toContain('const y = 2;'); + }); + + it('一致するコードブロックが見つからない場合、エラーをスローすること', () => { + const content = `\`\`\`typescript +const x = 1; +\`\`\``; + + expect(() => { + replaceCodeBlock(content, 'const z = 3;', 'const y = 2;'); + }).toThrow('一致するコードブロックが見つかりません'); + }); + + it('言語識別子を保持すること', () => { + const content = `\`\`\`javascript +const x = 1; +\`\`\``; + + const result = replaceCodeBlock(content, 'const x = 1;', 'const y = 2;'); + + expect(result).toContain('```javascript'); + expect(result).toContain('const y = 2;'); + }); + + it('言語識別子がない場合も処理できること', () => { + const content = `\`\`\` +const x = 1; +\`\`\``; + + const result = replaceCodeBlock(content, 'const x = 1;', 'const y = 2;'); + + expect(result).toContain('```'); + expect(result).toContain('const y = 2;'); + }); + + it('複数のコードブロックがある場合、最初のマッチを置換すること', () => { + const content = `\`\`\`typescript +const x = 1; +\`\`\` + +\`\`\`typescript +const z = 3; +\`\`\``; + + const result = replaceCodeBlock(content, 'const x = 1;', 'const y = 2;'); + + expect(result).toContain('const y = 2;'); + expect(result).toContain('const z = 3;'); + expect(result).not.toContain('const x = 1;'); + }); + }); + + describe('findCodeBlockPosition', () => { + it('CODE_REFコメント後のコードブロック位置を検索すること', () => { + const content = `# Title + + + +\`\`\`typescript +const x = 1; +\`\`\``; + + const result = findCodeBlockPosition(content, ''); + + expect(result).not.toBeNull(); + expect(result?.language).toBe('typescript'); + }); + + it('CODE_REFコメントが見つからない場合、nullを返すこと', () => { + const content = `# Title`; + + const result = findCodeBlockPosition(content, ''); + + expect(result).toBeNull(); + }); + + it('CODE_REFコメントの終了タグがない場合、nullを返すこと', () => { + const content = ` + +Some text without code block`; + + const result = findCodeBlockPosition(content, ''); + + expect(result).toBeNull(); + }); + + it('言語識別子がないコードブロックも検索できること', () => { + const content = ` + +\`\`\` +const x = 1; +\`\`\``; + + const result = findCodeBlockPosition(content, ''); + + expect(result).not.toBeNull(); + expect(result?.language).toBe('typescript'); // デフォルト + }); + }); + + describe('moveCodeRefCommentBeforeCodeBlock', () => { + it('CODE_REFコメントをコードブロックの直前に移動すること', () => { + const content = `# Title + + + +Some text + +\`\`\`typescript +const x = 1; +\`\`\``; + + const codeBlockStart = content.indexOf('```typescript'); + const result = moveCodeRefCommentBeforeCodeBlock( + content, + '', + codeBlockStart + ); + + expect(result).toContain('\n```typescript'); + expect(result).not.toMatch(/\s+Some text/); + }); + + it('CODE_REFコメントが見つからない場合、エラーをスローすること', () => { + const content = `# Title + +\`\`\`typescript +const x = 1; +\`\`\``; + + expect(() => { + moveCodeRefCommentBeforeCodeBlock(content, '', 10); + }).toThrow('CODE_REFコメントが見つかりません'); + }); + + it('CODE_REFコメントの終了タグがない場合、エラーをスローすること', () => { + const content = ` +\`\`\`typescript +const x = 1; +\`\`\``; + + const codeBlockStart = content.indexOf('```typescript'); + const result = moveCodeRefCommentBeforeCodeBlock( + content, + '', + codeBlockStart + ); + + expect(result).toContain('\n```typescript'); + }); + + it('コメントの前後の改行を適切に処理すること', () => { + const content = `Some text + + + + +More text + +\`\`\`typescript +const x = 1; +\`\`\``; + + const codeBlockStart = content.indexOf('```typescript'); + const result = moveCodeRefCommentBeforeCodeBlock( + content, + '', + codeBlockStart + ); + + expect(result).toContain('More text'); + expect(result).toContain('\n```typescript'); + }); + }); +}); diff --git a/src/utils/markdown.ts b/src/utils/markdown.ts index 77bd691..011a301 100644 --- a/src/utils/markdown.ts +++ b/src/utils/markdown.ts @@ -65,10 +65,7 @@ export function extractCodeBlockAfterComment(content: string, commentIndex: numb export function associateCodeBlocksWithRefs(content: string, refs: CodeRef[]): CodeRef[] { return refs.map((ref) => { // codeBlockStartOffsetが設定されている場合はそれを使用、なければfullMatchから検索 - const commentIndex = - ref.codeBlockStartOffset !== undefined - ? ref.codeBlockStartOffset - : content.indexOf(ref.fullMatch); + const commentIndex = ref.codeBlockStartOffset ?? content.indexOf(ref.fullMatch); if (commentIndex === -1) { // fullMatchが見つからない場合(通常は発生しないはず) diff --git a/src/utils/prompt.test.ts b/src/utils/prompt.test.ts new file mode 100644 index 0000000..ee6db12 --- /dev/null +++ b/src/utils/prompt.test.ts @@ -0,0 +1,382 @@ +import * as fs from 'fs'; +import * as readline from 'readline'; + +import { jest } from '@jest/globals'; + +import { + askQuestion, + askSelectOption, + askYesNo, + createPromptInterface, + displayFixPreview, +} from './prompt'; +import type { FixAction } from './types'; + +// モジュールをモック +jest.mock('fs'); +jest.mock('readline'); + +const mockedFs = fs as jest.Mocked; +const mockedReadline = readline as jest.Mocked; + +describe('prompt', () => { + let mockRl: jest.Mocked; + + beforeEach(() => { + jest.clearAllMocks(); + + // readline.Interface のモックを作成 + mockRl = { + question: jest.fn(), + close: jest.fn(), + on: jest.fn(), + } as any; + }); + + describe('createPromptInterface', () => { + it('readline.Interfaceを作成すること', () => { + mockedReadline.createInterface.mockReturnValue(mockRl as any); + + const result = createPromptInterface(); + + expect(result).toBe(mockRl); + expect(mockedReadline.createInterface).toHaveBeenCalledWith({ + input: process.stdin, + output: process.stdout, + }); + }); + }); + + describe('askQuestion', () => { + it('質問して回答を取得すること', async () => { + const answer = 'test answer'; + mockRl.question.mockImplementation((_q, callback) => { + (callback as any)(answer); + }); + + const result = await askQuestion(mockRl, 'What is your name?'); + + expect(result).toBe(answer); + expect(mockRl.question).toHaveBeenCalledWith('What is your name?', expect.any(Function)); + }); + + it('空の回答も正しく処理すること', async () => { + mockRl.question.mockImplementation((_q, callback) => { + (callback as any)(''); + }); + + const result = await askQuestion(mockRl, 'Optional question?'); + + expect(result).toBe(''); + }); + }); + + describe('askYesNo', () => { + it('yesの回答でtrueを返すこと', async () => { + mockRl.question.mockImplementation((_q, callback) => { + (callback as any)('y'); + }); + + const result = await askYesNo(mockRl, 'Continue?'); + + expect(result).toBe(true); + }); + + it('Yesの回答でtrueを返すこと(大文字小文字を区別しない)', async () => { + mockRl.question.mockImplementation((_q, callback) => { + (callback as any)('Yes'); + }); + + const result = await askYesNo(mockRl, 'Continue?'); + + expect(result).toBe(true); + }); + + it('noの回答でfalseを返すこと', async () => { + mockRl.question.mockImplementation((_q, callback) => { + (callback as any)('n'); + }); + + const result = await askYesNo(mockRl, 'Continue?'); + + expect(result).toBe(false); + }); + + it('空の回答でデフォルト値を返すこと(デフォルトfalse)', async () => { + mockRl.question.mockImplementation((_q, callback) => { + (callback as any)(''); + }); + + const result = await askYesNo(mockRl, 'Continue?', false); + + expect(result).toBe(false); + }); + + it('空の回答でデフォルト値を返すこと(デフォルトtrue)', async () => { + mockRl.question.mockImplementation((_q, callback) => { + (callback as any)(''); + }); + + const result = await askYesNo(mockRl, 'Continue?', true); + + expect(result).toBe(true); + }); + + it('質問文にデフォルト値が含まれること(デフォルトfalse)', async () => { + mockRl.question.mockImplementation((_q, callback) => { + (callback as any)('y'); + }); + + await askYesNo(mockRl, 'Continue?', false); + + expect(mockRl.question).toHaveBeenCalledWith('Continue? (y/N): ', expect.any(Function)); + }); + + it('質問文にデフォルト値が含まれること(デフォルトtrue)', async () => { + mockRl.question.mockImplementation((_q, callback) => { + (callback as any)('n'); + }); + + await askYesNo(mockRl, 'Continue?', true); + + expect(mockRl.question).toHaveBeenCalledWith('Continue? (Y/n): ', expect.any(Function)); + }); + }); + + describe('askSelectOption', () => { + it('有効なオプションを選択できること', async () => { + const options = ['Option 1', 'Option 2', 'Option 3']; + const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); + + mockRl.question.mockImplementation((_q, callback) => { + (callback as any)('2'); + }); + + const result = await askSelectOption(mockRl, options, 'Choose an option:'); + + expect(result).toBe(1); // 0-based index + expect(consoleLogSpy).toHaveBeenCalledWith('\nChoose an option:'); + expect(consoleLogSpy).toHaveBeenCalledWith(' 1) Option 1'); + expect(consoleLogSpy).toHaveBeenCalledWith(' 2) Option 2'); + expect(consoleLogSpy).toHaveBeenCalledWith(' 3) Option 3'); + + consoleLogSpy.mockRestore(); + }); + + it('無効な選択の後、有効な選択ができること', async () => { + const options = ['Option 1', 'Option 2']; + const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); + + let callCount = 0; + mockRl.question.mockImplementation((_q, callback) => { + callCount++; + if (callCount === 1) { + (callback as any)('5'); // 無効な選択 + } else { + (callback as any)('1'); // 有効な選択 + } + }); + + const result = await askSelectOption(mockRl, options, 'Choose:'); + + expect(result).toBe(0); + expect(consoleLogSpy).toHaveBeenCalledWith('❌ 無効な選択です。もう一度入力してください。'); + + consoleLogSpy.mockRestore(); + }); + + it('0を入力した場合、再入力を求めること', async () => { + const options = ['Option 1', 'Option 2']; + const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); + + let callCount = 0; + mockRl.question.mockImplementation((_q, callback) => { + callCount++; + if (callCount === 1) { + (callback as any)('0'); // 無効 + } else { + (callback as any)('1'); // 有効 + } + }); + + const result = await askSelectOption(mockRl, options, 'Choose:'); + + expect(result).toBe(0); + expect(consoleLogSpy).toHaveBeenCalledWith('❌ 無効な選択です。もう一度入力してください。'); + + consoleLogSpy.mockRestore(); + }); + }); + + describe('displayFixPreview', () => { + let consoleLogSpy: any; + + beforeEach(() => { + consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); + }); + + afterEach(() => { + consoleLogSpy.mockRestore(); + }); + + it('UPDATE_LINE_NUMBERSタイプの修正プレビューを表示すること', () => { + const action: FixAction = { + type: 'UPDATE_LINE_NUMBERS', + error: { + type: 'CODE_LOCATION_MISMATCH', + message: 'Code location mismatch', + ref: { + fullMatch: '', + refPath: 'src/test.ts', + startLine: 10, + endLine: 20, + docFile: 'test.md', + }, + }, + description: 'Update line numbers', + preview: 'Preview text', + newStartLine: 15, + newEndLine: 25, + }; + + displayFixPreview(action); + + expect(consoleLogSpy).toHaveBeenCalledWith('\n変更内容:'); + expect(consoleLogSpy).toHaveBeenCalledWith('- 説明: Update line numbers'); + }); + + it('INSERT_CODE_BLOCKタイプの修正プレビューを表示すること', () => { + const action: FixAction = { + type: 'INSERT_CODE_BLOCK', + error: { + type: 'INSERT_CODE_BLOCK', + message: 'Insert code block', + ref: { + fullMatch: '', + refPath: 'src/test.ts', + startLine: 10, + endLine: 20, + docFile: 'test.md', + }, + }, + description: 'Insert code block', + preview: 'Preview text', + newCodeBlock: 'const x = 1;', + }; + + displayFixPreview(action); + + expect(consoleLogSpy).toHaveBeenCalledWith('\n変更内容:'); + expect(consoleLogSpy).toHaveBeenCalledWith('- 説明: Insert code block'); + expect(consoleLogSpy).toHaveBeenCalledWith('\x1b[32m+ コードブロックを挿入:\x1b[0m'); + }); + + it('CODE_CONTENT_MISMATCHタイプの修正プレビューを表示すること', () => { + const action: FixAction = { + type: 'REPLACE_CODE_BLOCK', + error: { + type: 'CODE_CONTENT_MISMATCH', + message: 'Code content mismatch', + ref: { + fullMatch: '', + refPath: 'src/test.ts', + startLine: 10, + endLine: 20, + docFile: 'test.md', + }, + expectedCode: 'const x = 1;', + actualCode: 'const y = 2;', + }, + description: 'Replace code block', + preview: 'Preview text', + newCodeBlock: 'const y = 2;', + }; + + displayFixPreview(action); + + expect(consoleLogSpy).toHaveBeenCalledWith('\n変更内容:'); + expect(consoleLogSpy).toHaveBeenCalledWith('- 説明: Replace code block'); + }); + + it('CODE_LOCATION_MISMATCHタイプの修正プレビューを表示すること(コードブロックなし)', () => { + const action: FixAction = { + type: 'UPDATE_LINE_NUMBERS', + error: { + type: 'CODE_LOCATION_MISMATCH', + message: 'Code location mismatch', + ref: { + fullMatch: '', + refPath: 'src/test.ts', + startLine: 10, + endLine: 20, + docFile: 'test.md', + }, + }, + description: 'Update line numbers', + preview: 'Simple preview text', + }; + + displayFixPreview(action); + + expect(consoleLogSpy).toHaveBeenCalledWith('\n変更内容:'); + expect(consoleLogSpy).toHaveBeenCalledWith('- 説明: Update line numbers'); + expect(consoleLogSpy).toHaveBeenCalledWith('Simple preview text'); + }); + + it('REPLACE_CODE_BLOCKタイプの修正プレビューを表示すること', () => { + const action: FixAction = { + type: 'REPLACE_CODE_BLOCK', + error: { + type: 'REPLACE_CODE_BLOCK', + message: 'Replace code block', + ref: { + fullMatch: '', + refPath: 'src/test.ts', + startLine: 10, + endLine: 20, + docFile: 'test.md', + }, + expectedCode: 'old code', + actualCode: 'new code', + }, + description: 'Replace code block', + preview: 'Preview text', + newCodeBlock: 'new code', + }; + + displayFixPreview(action); + + expect(consoleLogSpy).toHaveBeenCalledWith('\n変更内容:'); + expect(consoleLogSpy).toHaveBeenCalledWith('- 説明: Replace code block'); + }); + + it('UPDATE_END_LINEタイプの修正プレビューを表示すること', () => { + mockedFs.existsSync.mockReturnValue(true); + + const action: FixAction = { + type: 'UPDATE_END_LINE', + error: { + type: 'UPDATE_END_LINE', + message: 'Update end line', + ref: { + fullMatch: '', + refPath: 'src/test.ts', + startLine: 10, + endLine: 20, + docFile: 'test.md', + }, + }, + description: 'Update end line number', + preview: 'Preview text', + newStartLine: 10, + newEndLine: 25, + newCodeBlock: 'const x = 1;\nconst y = 2;\nconst z = 3;', + }; + + displayFixPreview(action); + + expect(consoleLogSpy).toHaveBeenCalledWith('\n変更内容:'); + expect(consoleLogSpy).toHaveBeenCalledWith('- 説明: Update end line number'); + }); + }); +}); From 0bd6ce944cf12dab453081b8953a3f38bfb6b531 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Tue, 30 Dec 2025 14:02:14 +0900 Subject: [PATCH 14/57] refactor: add baseUrl and alias in tsconfig.json --- src/cli/fix.ts | 12 ++++++------ src/cli/validate.ts | 10 +++++----- src/config.test.ts | 2 +- src/core/validate.test.ts | 6 +++--- src/core/validate.ts | 10 +++++----- src/index.ts | 12 ++++++------ src/utils/ast-scope-expansion.test.ts | 2 +- src/utils/ast-scope-expansion.ts | 2 +- src/utils/ast-symbol-search.test.ts | 4 ++-- src/utils/ast-symbol-search.ts | 2 +- src/utils/backup.test.ts | 2 +- src/utils/code-comparison.test.ts | 2 +- src/utils/code-comparison.ts | 6 +++--- src/utils/code-ellipsis.test.ts | 2 +- src/utils/code-ellipsis.ts | 2 +- src/utils/diff-display.test.ts | 2 +- src/utils/fix.test.ts | 12 ++++++------ src/utils/fix.ts | 12 ++++++------ src/utils/ignore-pattern.test.ts | 2 +- src/utils/markdown-edit.test.ts | 2 +- src/utils/markdown-edit.ts | 2 +- src/utils/markdown.test.ts | 4 ++-- src/utils/markdown.ts | 2 +- src/utils/prompt.test.ts | 4 ++-- src/utils/prompt.ts | 6 +++--- tsconfig.json | 6 +++++- 26 files changed, 67 insertions(+), 63 deletions(-) diff --git a/src/cli/fix.ts b/src/cli/fix.ts index e345a90..9c0d395 100644 --- a/src/cli/fix.ts +++ b/src/cli/fix.ts @@ -13,12 +13,12 @@ import * as fs from 'fs'; import * as path from 'path'; -import { createBackup } from '../utils/backup'; -import { applyFix, createFixAction, handleMultipleMatches, isFixableError } from '../utils/fix'; -import { askYesNo, createPromptInterface, displayFixPreview } from '../utils/prompt'; -import type { CodeRefError, FixOptions, FixResult } from '../utils/types'; -import { extractCodeRefs, findMarkdownFiles, validateCodeRef } from '../core/validate'; -import { loadFixConfig, getDocsPath, type CodeRefFixConfig } from '../config'; +import { createBackup } from '@/utils/backup'; +import { applyFix, createFixAction, handleMultipleMatches, isFixableError } from '@/utils/fix'; +import { askYesNo, createPromptInterface, displayFixPreview } from '@/utils/prompt'; +import type { CodeRefError, FixOptions, FixResult } from '@/utils/types'; +import { extractCodeRefs, findMarkdownFiles, validateCodeRef } from '@/core/validate'; +import { loadFixConfig, getDocsPath, type CodeRefFixConfig } from '@/config'; // コマンドライン引数のパース function parseArgs(): FixOptions { diff --git a/src/cli/validate.ts b/src/cli/validate.ts index e98194a..755a5f0 100644 --- a/src/cli/validate.ts +++ b/src/cli/validate.ts @@ -13,11 +13,11 @@ import * as fs from 'fs'; import * as path from 'path'; -import { loadConfig, getDocsPath, getIgnoreFilePath } from '../config'; -import { findMarkdownFiles, extractCodeRefs, validateCodeRef } from '../core/validate'; -import { isIgnored, loadDocsignorePatterns } from '../utils/ignore-pattern'; -import { displayLineRangeDiff } from '../utils/diff-display'; -import { extractLinesFromFile } from '../utils/code-comparison'; +import { loadConfig, getDocsPath, getIgnoreFilePath } from '@/config'; +import { findMarkdownFiles, extractCodeRefs, validateCodeRef } from '@/core/validate'; +import { isIgnored, loadDocsignorePatterns } from '@/utils/ignore-pattern'; +import { displayLineRangeDiff } from '@/utils/diff-display'; +import { extractLinesFromFile } from '@/utils/code-comparison'; /** * CLI options diff --git a/src/config.test.ts b/src/config.test.ts index 081e2ba..47d4c7e 100644 --- a/src/config.test.ts +++ b/src/config.test.ts @@ -7,7 +7,7 @@ import { getDocsPath, getIgnoreFilePath, type CodeRefConfig, -} from './config'; +} from '@/config'; describe('Config System', () => { const originalCwd = process.cwd(); diff --git a/src/core/validate.test.ts b/src/core/validate.test.ts index 9f180df..d704825 100644 --- a/src/core/validate.test.ts +++ b/src/core/validate.test.ts @@ -3,9 +3,9 @@ import * as path from 'path'; import { jest } from '@jest/globals'; -import type { CodeRef } from '../utils/types'; -import { extractCodeRefs, findMarkdownFiles, validateCodeRef } from './validate'; -import type { CodeRefConfig } from '../config'; +import type { CodeRef } from '@/utils/types'; +import { extractCodeRefs, findMarkdownFiles, validateCodeRef } from '@/core/validate'; +import type { CodeRefConfig } from '@/config'; // テスト用のモックプロジェクトルート const mockProjectRoot = '/project'; diff --git a/src/core/validate.ts b/src/core/validate.ts index 5e25b46..38bd050 100644 --- a/src/core/validate.ts +++ b/src/core/validate.ts @@ -10,15 +10,15 @@ import { isTypeScriptOrJavaScript, parseSymbolPath, selectBestSymbolMatch, -} from '../utils/ast-symbol-search'; +} from '@/utils/ast-symbol-search'; import { compareCodeContent, extractLinesFromFile, searchCodeInFile, -} from '../utils/code-comparison'; -import { associateCodeBlocksWithRefs } from '../utils/markdown'; -import type { CodeRef, CodeRefError } from '../utils/types'; -import { loadConfig, type CodeRefConfig } from '../config'; +} from '@/utils/code-comparison'; +import { associateCodeBlocksWithRefs } from '@/utils/markdown'; +import type { CodeRef, CodeRefError } from '@/utils/types'; +import { loadConfig, type CodeRefConfig } from '@/config'; // CODE_REF パターン定数 const CODE_REF_PATTERN = //g; diff --git a/src/index.ts b/src/index.ts index 9a0d9fe..af4e0ed 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,7 +21,7 @@ export { validateCodeRef, validateCodeContent, validateSymbolRef, -} from './core/validate'; +} from '@/core/validate'; // Configuration export { @@ -34,7 +34,7 @@ export { type CodeRefFixConfig, type PartialCodeRefConfig, type PartialCodeRefFixConfig, -} from './config'; +} from '@/config'; // Types export type { @@ -45,7 +45,7 @@ export type { FixOptions, FixResult, ExpandedMatch, -} from './utils/types'; +} from '@/utils/types'; // Utility functions export { @@ -53,12 +53,12 @@ export { findSymbolInAST, parseSymbolPath, selectBestSymbolMatch, -} from './utils/ast-symbol-search'; +} from '@/utils/ast-symbol-search'; export { compareCodeContent, extractLinesFromFile, searchCodeInFile, -} from './utils/code-comparison'; +} from '@/utils/code-comparison'; -export { isIgnored, loadDocsignorePatterns } from './utils/ignore-pattern'; +export { isIgnored, loadDocsignorePatterns } from '@/utils/ignore-pattern'; diff --git a/src/utils/ast-scope-expansion.test.ts b/src/utils/ast-scope-expansion.test.ts index a1eb245..717d36e 100644 --- a/src/utils/ast-scope-expansion.test.ts +++ b/src/utils/ast-scope-expansion.test.ts @@ -1,4 +1,4 @@ -import { expandMatchToScope } from './ast-scope-expansion'; +import { expandMatchToScope } from '@/utils/ast-scope-expansion'; describe('ast-scope-expansion', () => { describe('expandMatchToScope', () => { diff --git a/src/utils/ast-scope-expansion.ts b/src/utils/ast-scope-expansion.ts index 6ea7d0d..f1cb78b 100644 --- a/src/utils/ast-scope-expansion.ts +++ b/src/utils/ast-scope-expansion.ts @@ -7,7 +7,7 @@ import * as path from 'path'; import { parse as parseTypeScript } from '@typescript-eslint/typescript-estree'; import type { TSESTree } from '@typescript-eslint/typescript-estree'; -import type { ExpandedMatch } from './types'; +import type { ExpandedMatch } from '@/utils/types'; /** * マッチ拡張のオプション diff --git a/src/utils/ast-symbol-search.test.ts b/src/utils/ast-symbol-search.test.ts index dd90f41..af70f8c 100644 --- a/src/utils/ast-symbol-search.test.ts +++ b/src/utils/ast-symbol-search.test.ts @@ -4,8 +4,8 @@ import { isTypeScriptOrJavaScript, parseSymbolPath, selectBestSymbolMatch, -} from './ast-symbol-search'; -import type { SymbolMatch } from './types'; +} from '@/utils/ast-symbol-search'; +import type { SymbolMatch } from '@/utils/types'; describe('ast-symbol-search', () => { afterEach(() => { diff --git a/src/utils/ast-symbol-search.ts b/src/utils/ast-symbol-search.ts index 1f28087..a9b7aaa 100644 --- a/src/utils/ast-symbol-search.ts +++ b/src/utils/ast-symbol-search.ts @@ -7,7 +7,7 @@ import * as path from 'path'; import { parse as parseTypeScript } from '@typescript-eslint/typescript-estree'; import type { TSESTree } from '@typescript-eslint/typescript-estree'; -import type { SymbolMatch } from './types'; +import type { SymbolMatch } from '@/utils/types'; /** * ASTキャッシュ(同じファイルを複数回パースしない) diff --git a/src/utils/backup.test.ts b/src/utils/backup.test.ts index 94c18e9..b0a1fd6 100644 --- a/src/utils/backup.test.ts +++ b/src/utils/backup.test.ts @@ -2,7 +2,7 @@ import * as fs from 'fs'; import { jest } from '@jest/globals'; -import { createBackup, deleteBackup, listBackups, restoreBackup } from './backup'; +import { createBackup, deleteBackup, listBackups, restoreBackup } from '@/utils/backup'; // fsモジュールをモック jest.mock('fs'); diff --git a/src/utils/code-comparison.test.ts b/src/utils/code-comparison.test.ts index 7c717df..425d5e1 100644 --- a/src/utils/code-comparison.test.ts +++ b/src/utils/code-comparison.test.ts @@ -7,7 +7,7 @@ import { dedentCode, extractLinesFromFile, searchCodeInFile, -} from './code-comparison'; +} from '@/utils/code-comparison'; // fsモジュールをモック jest.mock('fs'); diff --git a/src/utils/code-comparison.ts b/src/utils/code-comparison.ts index aa3814c..5ad5bcb 100644 --- a/src/utils/code-comparison.ts +++ b/src/utils/code-comparison.ts @@ -4,9 +4,9 @@ import * as fs from 'fs'; -import { expandMatchToScope } from './ast-scope-expansion'; -import { normalizeCode } from './markdown'; -import type { ExpandedMatch } from './types'; +import { expandMatchToScope } from '@/utils/ast-scope-expansion'; +import { normalizeCode } from '@/utils/markdown'; +import type { ExpandedMatch } from '@/utils/types'; /** * コードから共通の先頭インデントを除去する diff --git a/src/utils/code-ellipsis.test.ts b/src/utils/code-ellipsis.test.ts index 41ed748..940d3ed 100644 --- a/src/utils/code-ellipsis.test.ts +++ b/src/utils/code-ellipsis.test.ts @@ -2,7 +2,7 @@ import * as fs from 'fs'; import { jest } from '@jest/globals'; -import { insertEllipsis, removeEllipsis } from './code-ellipsis'; +import { insertEllipsis, removeEllipsis } from '@/utils/code-ellipsis'; // fsモジュールをモック jest.mock('fs'); diff --git a/src/utils/code-ellipsis.ts b/src/utils/code-ellipsis.ts index 693bad5..d3ce567 100644 --- a/src/utils/code-ellipsis.ts +++ b/src/utils/code-ellipsis.ts @@ -7,7 +7,7 @@ import * as fs from 'fs'; import { parse as parseTypeScript } from '@typescript-eslint/typescript-estree'; import type { TSESTree } from '@typescript-eslint/typescript-estree'; -import { isTypeScriptOrJavaScript } from './ast-symbol-search'; +import { isTypeScriptOrJavaScript } from '@/utils/ast-symbol-search'; const ELLIPSIS = ' // ... (省略) ...'; diff --git a/src/utils/diff-display.test.ts b/src/utils/diff-display.test.ts index 9f86be6..4c0ff22 100644 --- a/src/utils/diff-display.test.ts +++ b/src/utils/diff-display.test.ts @@ -1,4 +1,4 @@ -import { displayCodeDiff, displayLineRangeDiff, truncateText } from './diff-display'; +import { displayCodeDiff, displayLineRangeDiff, truncateText } from '@/utils/diff-display'; describe('displayCodeDiff', () => { it('一致するコードの場合、差分を表示しない', () => { diff --git a/src/utils/fix.test.ts b/src/utils/fix.test.ts index e10be95..7b2185f 100644 --- a/src/utils/fix.test.ts +++ b/src/utils/fix.test.ts @@ -5,7 +5,7 @@ import * as fs from 'fs'; import type * as readline from 'readline'; -import { extractLinesFromFile, searchCodeInFileWithScopeExpansion } from './code-comparison'; +import { extractLinesFromFile, searchCodeInFileWithScopeExpansion } from '@/utils/code-comparison'; import { applyFix, createBlockMissingFix, @@ -17,14 +17,14 @@ import { createSymbolRangeMismatchFix, handleMultipleMatches, isFixableError, -} from './fix'; -import * as markdownEdit from './markdown-edit'; -import * as prompt from './prompt'; -import type { CodeRefError, FixAction } from './types'; +} from '@/utils/fix'; +import * as markdownEdit from '@/utils/markdown-edit'; +import * as prompt from '@/utils/prompt'; +import type { CodeRefError, FixAction } from '@/utils/types'; // モック設定 jest.mock('fs'); -jest.mock('./code-comparison', () => ({ +jest.mock('@/utils/code-comparison', () => ({ searchCodeInFile: jest.fn(), searchCodeInFileWithScopeExpansion: jest.fn(), extractLinesFromFile: jest.fn(), diff --git a/src/utils/fix.ts b/src/utils/fix.ts index d344829..1f87fc6 100644 --- a/src/utils/fix.ts +++ b/src/utils/fix.ts @@ -6,18 +6,18 @@ import * as fs from 'fs'; import * as path from 'path'; import type * as readline from 'readline'; -import { expandMatchToScope } from './ast-scope-expansion'; -import { findSymbolInAST } from './ast-symbol-search'; -import { extractLinesFromFile, searchCodeInFileWithScopeExpansion } from './code-comparison'; +import { expandMatchToScope } from '@/utils/ast-scope-expansion'; +import { findSymbolInAST } from '@/utils/ast-symbol-search'; +import { extractLinesFromFile, searchCodeInFileWithScopeExpansion } from '@/utils/code-comparison'; import { findCodeBlockPosition, insertCodeBlockAfterComment, moveCodeRefCommentBeforeCodeBlock, replaceCodeBlock, replaceCodeRefComment, -} from './markdown-edit'; -import { askSelectOption } from './prompt'; -import type { CodeRefError, ExpandedMatch, FixAction } from './types'; +} from '@/utils/markdown-edit'; +import { askSelectOption } from '@/utils/prompt'; +import type { CodeRefError, ExpandedMatch, FixAction } from '@/utils/types'; /** * エラーが修正可能かチェック diff --git a/src/utils/ignore-pattern.test.ts b/src/utils/ignore-pattern.test.ts index da238c0..005131c 100644 --- a/src/utils/ignore-pattern.test.ts +++ b/src/utils/ignore-pattern.test.ts @@ -1,6 +1,6 @@ import * as fs from 'fs'; -import { isIgnored, loadDocsignorePatterns, matchesPattern } from './ignore-pattern'; +import { isIgnored, loadDocsignorePatterns, matchesPattern } from '@/utils/ignore-pattern'; // fsをモック jest.mock('fs'); diff --git a/src/utils/markdown-edit.test.ts b/src/utils/markdown-edit.test.ts index e46ef43..fbcd1f2 100644 --- a/src/utils/markdown-edit.test.ts +++ b/src/utils/markdown-edit.test.ts @@ -4,7 +4,7 @@ import { moveCodeRefCommentBeforeCodeBlock, replaceCodeBlock, replaceCodeRefComment, -} from './markdown-edit'; +} from '@/utils/markdown-edit'; describe('markdown-edit', () => { describe('replaceCodeRefComment', () => { diff --git a/src/utils/markdown-edit.ts b/src/utils/markdown-edit.ts index 216cdfa..cd3d4fc 100644 --- a/src/utils/markdown-edit.ts +++ b/src/utils/markdown-edit.ts @@ -2,7 +2,7 @@ * マークダウン編集ユーティリティ */ -import { normalizeCode } from './markdown'; +import { normalizeCode } from '@/utils/markdown'; /** * CODE_REFコメントを置換 diff --git a/src/utils/markdown.test.ts b/src/utils/markdown.test.ts index 0b286c2..f4d3d39 100644 --- a/src/utils/markdown.test.ts +++ b/src/utils/markdown.test.ts @@ -4,8 +4,8 @@ import { associateCodeBlocksWithRefs, extractCodeBlockAfterComment, normalizeCode, -} from './markdown'; -import type { CodeRef } from './types'; +} from '@/utils/markdown'; +import type { CodeRef } from '@/utils/types'; describe('markdown.utils', () => { describe('extractCodeBlockAfterComment', () => { diff --git a/src/utils/markdown.ts b/src/utils/markdown.ts index 011a301..5e9f936 100644 --- a/src/utils/markdown.ts +++ b/src/utils/markdown.ts @@ -2,7 +2,7 @@ * マークダウンドキュメント処理ユーティリティ */ -import type { CodeRef } from './types'; +import type { CodeRef } from '@/utils/types'; /** * CODE_REFコメントの後にあるコードブロックを抽出する diff --git a/src/utils/prompt.test.ts b/src/utils/prompt.test.ts index ee6db12..4e43c5b 100644 --- a/src/utils/prompt.test.ts +++ b/src/utils/prompt.test.ts @@ -9,8 +9,8 @@ import { askYesNo, createPromptInterface, displayFixPreview, -} from './prompt'; -import type { FixAction } from './types'; +} from '@/utils/prompt'; +import type { FixAction } from '@/utils/types'; // モジュールをモック jest.mock('fs'); diff --git a/src/utils/prompt.ts b/src/utils/prompt.ts index 0b6521c..91ce440 100644 --- a/src/utils/prompt.ts +++ b/src/utils/prompt.ts @@ -6,9 +6,9 @@ import * as fs from 'fs'; import * as path from 'path'; import * as readline from 'readline'; -import { extractLinesFromFile } from './code-comparison'; -import { displayCodeDiff, displayLineRangeDiff } from './diff-display'; -import type { FixAction } from './types'; +import { extractLinesFromFile } from '@/utils/code-comparison'; +import { displayCodeDiff, displayLineRangeDiff } from '@/utils/diff-display'; +import type { FixAction } from '@/utils/types'; /** * Readlineインターフェースを作成 diff --git a/tsconfig.json b/tsconfig.json index 05baa34..16c06f8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,10 @@ "checkJs": false, "outDir": "./dist", "rootDir": "./src", + "baseUrl": "./", + "paths": { + "@/*": ["src/*"] + }, "removeComments": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, @@ -22,5 +26,5 @@ "sourceMap": true }, "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "**/*.test.ts"] + "exclude": ["node_modules", "dist"] } From 46e0c321d6c99faeda89dc1d164a80cee159b89a Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Tue, 30 Dec 2025 14:06:42 +0900 Subject: [PATCH 15/57] ci: exec test before push to git --- .husky/pre-push | 1 + 1 file changed, 1 insertion(+) create mode 100755 .husky/pre-push diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 0000000..e37998f --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1 @@ +npm run test From 8fa4c9973900e43e0bd8f282bd678f2c378ac3d1 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Tue, 30 Dec 2025 15:14:31 +0900 Subject: [PATCH 16/57] ci: add github action for lint and format --- .github/workflows/lint.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..3fa0bb7 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,37 @@ +name: Lint, Format Check, and Type Check + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + code-quality: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [22.x] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Run ESLint + run: npm run lint + + - name: Check code formatting + run: npm run format:check + + - name: Run type check + run: npm run type-check From 50dc2c5ebb293d785e86fada5226d3e87306f331 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Tue, 30 Dec 2025 15:20:37 +0900 Subject: [PATCH 17/57] ci: add github action for test --- .github/workflows/test.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..94a68d7 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,31 @@ +name: Test + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [22.x] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm run test From 453846d6d1aaee4b807ef472d946b7d7a9ff1107 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Tue, 30 Dec 2025 15:25:58 +0900 Subject: [PATCH 18/57] "Claude PR Assistant workflow" --- .github/workflows/claude.yml | 50 ++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .github/workflows/claude.yml diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml new file mode 100644 index 0000000..d300267 --- /dev/null +++ b/.github/workflows/claude.yml @@ -0,0 +1,50 @@ +name: Claude Code + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + issues: + types: [opened, assigned] + pull_request_review: + types: [submitted] + +jobs: + claude: + if: | + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || + (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + actions: read # Required for Claude to read CI results on PRs + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code + id: claude + uses: anthropics/claude-code-action@v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + + # This is an optional setting that allows Claude to read CI results on PRs + additional_permissions: | + actions: read + + # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it. + # prompt: 'Update the pull request description to include a summary of changes.' + + # Optional: Add claude_args to customize behavior and configuration + # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md + # or https://code.claude.com/docs/en/cli-reference for available options + # claude_args: '--allowed-tools Bash(gh pr:*)' + From 6fd0c593d12e3d8331552deb1e9bc920512000d7 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Tue, 30 Dec 2025 15:25:59 +0900 Subject: [PATCH 19/57] "Claude Code Review workflow" --- .github/workflows/claude-code-review.yml | 57 ++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 .github/workflows/claude-code-review.yml diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml new file mode 100644 index 0000000..8452b0f --- /dev/null +++ b/.github/workflows/claude-code-review.yml @@ -0,0 +1,57 @@ +name: Claude Code Review + +on: + pull_request: + types: [opened, synchronize] + # Optional: Only run on specific file changes + # paths: + # - "src/**/*.ts" + # - "src/**/*.tsx" + # - "src/**/*.js" + # - "src/**/*.jsx" + +jobs: + claude-review: + # Optional: Filter by PR author + # if: | + # github.event.pull_request.user.login == 'external-contributor' || + # github.event.pull_request.user.login == 'new-developer' || + # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' + + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code Review + id: claude-review + uses: anthropics/claude-code-action@v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + prompt: | + REPO: ${{ github.repository }} + PR NUMBER: ${{ github.event.pull_request.number }} + + Please review this pull request and provide feedback on: + - Code quality and best practices + - Potential bugs or issues + - Performance considerations + - Security concerns + - Test coverage + + Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback. + + Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR. + + # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md + # or https://code.claude.com/docs/en/cli-reference for available options + claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"' + From 624f665ddc08f213615d46a05cd2779dced33a73 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Tue, 30 Dec 2025 15:28:37 +0900 Subject: [PATCH 20/57] style: translate japanese of claude code review --- .github/workflows/claude-code-review.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 8452b0f..de529e4 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -36,22 +36,22 @@ jobs: uses: anthropics/claude-code-action@v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + github_token: ${{ secrets.GITHUB_TOKEN }} prompt: | REPO: ${{ github.repository }} PR NUMBER: ${{ github.event.pull_request.number }} - Please review this pull request and provide feedback on: - - Code quality and best practices - - Potential bugs or issues - - Performance considerations - - Security concerns - - Test coverage + このプルリクエストをレビューし、以下の観点についてフィードバックを提供してください。 + - コード品質およびベストプラクティス + - 潜在的なバグや問題点 + - パフォーマンスに関する懸念 + - セキュリティ上の懸念 + - テストカバレッジ + - ドキュメント更新の必要性 - Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback. - - Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR. + スタイルやコーディング規約については、リポジトリ内の `CLAUDE.md` をガイドとして参照してください。フィードバックは建設的かつ有益なものにしてください。 + レビュー内容は、Bashツールを使って `gh pr comment` コマンドでPRにコメントとして投稿してください。 # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md # or https://code.claude.com/docs/en/cli-reference for available options claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"' - From 6cd58439db0fdf895c43d28bf8e637c12920605f Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Tue, 30 Dec 2025 20:55:18 +0900 Subject: [PATCH 21/57] docs: split documents from CLAUDE.md --- .gitignore | 1 + CLAUDE.md | 135 ++----------- docs/README.md | 45 +++++ docs/architecture/code-ref-syntax.md | 13 ++ docs/architecture/overview.md | 15 ++ docs/architecture/testing.md | 12 ++ docs/development/coding-standards.md | 43 ++++ docs/development/getting-started.md | 30 +++ docs/development/git-conventions.md | 29 +++ docs/development/testing-guide.md | 29 +++ docs/user-guide/cli-usage.md | 91 +++++++++ docs/user-guide/code-ref-syntax.md | 211 ++++++++++++++++++++ docs/user-guide/configuration.md | 284 +++++++++++++++++++++++++++ docs/user-guide/installation.md | 22 +++ src/index.ts | 1 + 15 files changed, 846 insertions(+), 115 deletions(-) create mode 100644 docs/README.md create mode 100644 docs/architecture/code-ref-syntax.md create mode 100644 docs/architecture/overview.md create mode 100644 docs/architecture/testing.md create mode 100644 docs/development/coding-standards.md create mode 100644 docs/development/getting-started.md create mode 100644 docs/development/git-conventions.md create mode 100644 docs/development/testing-guide.md create mode 100644 docs/user-guide/cli-usage.md create mode 100644 docs/user-guide/code-ref-syntax.md create mode 100644 docs/user-guide/configuration.md create mode 100644 docs/user-guide/installation.md diff --git a/.gitignore b/.gitignore index d863396..330c316 100644 --- a/.gitignore +++ b/.gitignore @@ -125,6 +125,7 @@ dist # Stores VSCode versions used for testing VSCode extensions .vscode/ .vscode-test +!.vscode/settings.json # Claude.ai files .claude/ diff --git a/CLAUDE.md b/CLAUDE.md index 7898bb8..271be0a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,58 +6,17 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co `@cawpea/coderef` is a tool for validating and auto-fixing code references in markdown documentation. It ensures code snippets in documentation stay synchronized with actual source code through CODE_REF comments and AST-based symbol searching. -## Development Commands +## Quick Reference -### Building +- **Architecture**: See [docs/architecture/overview.md](docs/architecture/overview.md) +- **Development Setup**: See [docs/development/getting-started.md](docs/development/getting-started.md) +- **Coding Standards**: See [docs/development/coding-standards.md](docs/development/coding-standards.md) +- **Git Conventions**: See [docs/development/git-conventions.md](docs/development/git-conventions.md) +- **Testing**: See [docs/development/testing-guide.md](docs/development/testing-guide.md) -```bash -npm run build # Build for production (CJS + ESM + types) -npm run dev # Watch mode for development -``` - -### Testing - -```bash -npm test # Run all tests -npm run test:watch # Run tests in watch mode -npm run test:coverage # Run tests with coverage report -``` - -Coverage thresholds are set to 80% for all metrics (branches, functions, lines, statements). CLI code is excluded from coverage as it's verified through integration tests. - -### Linting & Formatting - -```bash -npm run lint # Lint TypeScript files -npm run lint:fix # Auto-fix linting issues -npm run format # Format code with Prettier -npm run format:check # Check formatting without modifying -npm run type-check # Run TypeScript compiler checks -``` - -#### Pre-commit Hooks - -This project uses [husky](https://typicode.github.io/husky/) and [lint-staged](https://github.com/okonet/lint-staged) to automatically lint and format staged files before each commit. - -The pre-commit hook will: - -1. Run ESLint with auto-fix on staged TypeScript files -2. Run Prettier on staged files (TS, JS, JSON, MD) -3. Prevent commit if linting errors remain - -To skip hooks (not recommended): - -```bash -git commit --no-verify -``` - -#### Configuration Files - -- `prettier.config.js` - Code formatting rules -- `eslint.config.js` - Linting rules (ESLint 9 flat config) -- `.vscode/settings.json` - VS Code auto-format on save +## Essential Information -#### Code Style +### Code Style (Quick Reference) - **Indentation**: 2 spaces - **Semicolons**: Enabled @@ -67,82 +26,28 @@ git commit --no-verify - **Trailing commas**: ES5 style - **Type imports**: Prefer `import type` for type-only imports -### Single Test Execution +### Common Commands ```bash -npx jest src/utils/foo.test.ts # Run specific test file -npx jest -t "" # Run tests matching pattern +npm run build # Build for production (CJS + ESM + types) +npm test # Run all tests +npm run lint:fix # Auto-fix linting issues +npm run format # Format code with Prettier +npm run type-check # Run TypeScript compiler checks ``` -## Git Commit Message Convention - -This project follows [Conventional Commits](https://www.conventionalcommits.org/) specification for commit messages. - ### Commit Message Format +Follow [Conventional Commits](https://www.conventionalcommits.org/): + ``` : - -[optional body] - -[optional footer] ``` -### Commit Types - -| Type | Description | Semantic Version Impact | Examples | -| ---------- | --------------------------------------------------------------- | ----------------------- | ---------------------------------------- | -| `feat` | New feature | MINOR (1.x.0) | Add evaluation agent, new UI component | -| `fix` | Bug fix | PATCH (1.0.x) | Fix contrast ratio calculation error | -| `docs` | Documentation only changes | None | Update README, add comments | -| `style` | Changes that don't affect code meaning (whitespace, formatting) | None | Run Prettier, fix indentation | -| `refactor` | Code changes that neither fix bugs nor add features | None | Split function, rename variables | -| `test` | Adding or updating tests | None | Add unit tests, improve mocks | -| `chore` | Changes to build process or tools | None | Update dependencies, modify config files | -| `ci` | Changes to CI configuration files and scripts | None | Update GitHub Actions | -| `perf` | Performance improvements | PATCH (1.0.x) | Optimize API response time | -| `build` | Changes to build system or external dependencies | None | Modify Webpack config, add npm scripts | -| `revert` | Revert a previous commit | Depends on original | Revert previous commit | - -## Architecture - -### Build System - -- **tsup**: Generates both CJS and ESM formats with type declarations -- Output directory: `./dist` -- Entry points defined in package.json exports for proper dual-package support - -### Module Structure - -The project is organized into three main directories under `src/`: - -- `cli/`: Command-line interface implementations (validate.ts, fix.ts) -- `core/`: Core validation and fixing logic -- `utils/`: Shared utility functions - -### CODE_REF Syntax Patterns - -The tool supports three reference patterns: - -1. **Line-based references**: `` -2. **Symbol references**: `` -3. **Class method references**: `` - -### AST Parsing - -Uses `@typescript-eslint/typescript-estree` for TypeScript/JavaScript symbol searching and code extraction. - -### Test Configuration - -- Test files: Co-located with source files as `**/*.test.ts` -- Path alias: `@/` maps to `src/` -- Environment: Node.js -- Preset: ts-jest - -## Publishing +**Types**: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`, `ci`, `perf`, `build`, `revert` -The `prepublishOnly` script ensures both build and tests pass before publishing to npm. +See [docs/development/git-conventions.md](docs/development/git-conventions.md) for the complete type table and semantic versioning impact. -## Node Version +## Full Documentation -Minimum Node.js version: 16.0.0 +For comprehensive documentation, see [docs/README.md](docs/README.md). diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..1137d0b --- /dev/null +++ b/docs/README.md @@ -0,0 +1,45 @@ +# Documentation Index + +Welcome to the @cawpea/coderef documentation. This guide is organized into three main sections: + +## For Users + +Start here if you want to use the tool: + +- [Installation](user-guide/installation.md) - Install and setup +- [CLI Usage](user-guide/cli-usage.md) - Command-line interface +- [Configuration](user-guide/configuration.md) - Configuration options +- [CODE_REF Syntax](user-guide/code-ref-syntax.md) - Reference syntax guide + +## For Contributors + +Start here if you want to contribute: + +- [Getting Started](development/getting-started.md) - Setup development environment +- [Coding Standards](development/coding-standards.md) - Style guide and conventions +- [Git Conventions](development/git-conventions.md) - Commit message format +- [Testing Guide](development/testing-guide.md) - Writing and running tests + +## Architecture & Design + +Deep dive into technical implementation: + +- [Architecture Overview](architecture/overview.md) - System design and module structure +- [CODE_REF Syntax Implementation](architecture/code-ref-syntax.md) - AST parsing and symbol search +- [Testing Strategy](architecture/testing.md) - Test configuration and coverage + +## Quick Links + +- [Main README](../README.md) - Project homepage +- [CLAUDE.md](../CLAUDE.md) - AI assistant guidance +- [CHANGELOG](../CHANGELOG.md) - Version history + +## Documentation Structure + +``` +docs/ +├── README.md (this file) +├── user-guide/ # End-user documentation +├── development/ # Contributor guides +└── architecture/ # Technical deep-dives +``` diff --git a/docs/architecture/code-ref-syntax.md b/docs/architecture/code-ref-syntax.md new file mode 100644 index 0000000..c32465d --- /dev/null +++ b/docs/architecture/code-ref-syntax.md @@ -0,0 +1,13 @@ +# CODE_REF Syntax Implementation + +## Supported Reference Patterns + +The tool supports three reference patterns: + +1. **Line-based references**: `` +2. **Symbol references**: `` +3. **Class method references**: `` + +## AST Parsing + +Uses `@typescript-eslint/typescript-estree` for TypeScript/JavaScript symbol searching and code extraction. diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md new file mode 100644 index 0000000..b2bcab9 --- /dev/null +++ b/docs/architecture/overview.md @@ -0,0 +1,15 @@ +# Architecture Overview + +## Build System + +- **tsup**: Generates both CJS and ESM formats with type declarations +- Output directory: `./dist` +- Entry points defined in package.json exports for proper dual-package support + +## Module Structure + +The project is organized into three main directories under `src/`: + +- `cli/`: Command-line interface implementations (validate.ts, fix.ts) +- `core/`: Core validation and fixing logic +- `utils/`: Shared utility functions diff --git a/docs/architecture/testing.md b/docs/architecture/testing.md new file mode 100644 index 0000000..0de29c4 --- /dev/null +++ b/docs/architecture/testing.md @@ -0,0 +1,12 @@ +# Testing Strategy + +## Test Configuration + +- Test files: Co-located with source files as `**/*.test.ts` +- Path alias: `@/` maps to `src/` +- Environment: Node.js +- Preset: ts-jest + +## Coverage Requirements + +Coverage thresholds are set to 80% for all metrics (branches, functions, lines, statements). CLI code is excluded from coverage as it's verified through integration tests. diff --git a/docs/development/coding-standards.md b/docs/development/coding-standards.md new file mode 100644 index 0000000..39ed865 --- /dev/null +++ b/docs/development/coding-standards.md @@ -0,0 +1,43 @@ +# Coding Standards + +## Linting & Formatting + +```bash +npm run lint # Lint TypeScript files +npm run lint:fix # Auto-fix linting issues +npm run format # Format code with Prettier +npm run format:check # Check formatting without modifying +npm run type-check # Run TypeScript compiler checks +``` + +## Pre-commit Hooks + +This project uses [husky](https://typicode.github.io/husky/) and [lint-staged](https://github.com/okonet/lint-staged) to automatically lint and format staged files before each commit. + +The pre-commit hook will: + +1. Run ESLint with auto-fix on staged TypeScript files +2. Run Prettier on staged files (TS, JS, JSON, MD) +3. Prevent commit if linting errors remain + +To skip hooks (not recommended): + +```bash +git commit --no-verify +``` + +## Configuration Files + +- `prettier.config.js` - Code formatting rules +- `eslint.config.js` - Linting rules (ESLint 9 flat config) +- `.vscode/settings.json` - VS Code auto-format on save + +## Code Style + +- **Indentation**: 2 spaces +- **Semicolons**: Enabled +- **Quotes**: Single quotes +- **Print width**: 100 characters +- **Line endings**: LF (Unix) +- **Trailing commas**: ES5 style +- **Type imports**: Prefer `import type` for type-only imports diff --git a/docs/development/getting-started.md b/docs/development/getting-started.md new file mode 100644 index 0000000..14f190a --- /dev/null +++ b/docs/development/getting-started.md @@ -0,0 +1,30 @@ +# Getting Started + +## Prerequisites + +- **Minimum Node.js version**: 16.0.0 (specified in `package.json` engines) +- **Recommended version**: See `.node-version` file in the project root + +## Building + +```bash +npm run build # Build for production (CJS + ESM + types) +npm run dev # Watch mode for development +``` + +## Testing + +```bash +npm test # Run all tests +npm run test:watch # Run tests in watch mode +npm run test:coverage # Run tests with coverage report +``` + +Coverage thresholds are set to 80% for all metrics (branches, functions, lines, statements). CLI code is excluded from coverage as it's verified through integration tests. + +## Running Specific Tests + +```bash +npx jest src/utils/foo.test.ts # Run specific test file +npx jest -t "" # Run tests matching pattern +``` diff --git a/docs/development/git-conventions.md b/docs/development/git-conventions.md new file mode 100644 index 0000000..95fe874 --- /dev/null +++ b/docs/development/git-conventions.md @@ -0,0 +1,29 @@ +# Git Commit Message Convention + +This project follows [Conventional Commits](https://www.conventionalcommits.org/) specification for commit messages. + +## Commit Message Format + +``` +: + +[optional body] + +[optional footer] +``` + +## Commit Types + +| Type | Description | Semantic Version Impact | Examples | +| ---------- | --------------------------------------------------------------- | ----------------------- | ---------------------------------------- | +| `feat` | New feature | MINOR (1.x.0) | Add evaluation agent, new UI component | +| `fix` | Bug fix | PATCH (1.0.x) | Fix contrast ratio calculation error | +| `docs` | Documentation only changes | None | Update README, add comments | +| `style` | Changes that don't affect code meaning (whitespace, formatting) | None | Run Prettier, fix indentation | +| `refactor` | Code changes that neither fix bugs nor add features | None | Split function, rename variables | +| `test` | Adding or updating tests | None | Add unit tests, improve mocks | +| `chore` | Changes to build process or tools | None | Update dependencies, modify config files | +| `ci` | Changes to CI configuration files and scripts | None | Update GitHub Actions | +| `perf` | Performance improvements | PATCH (1.0.x) | Optimize API response time | +| `build` | Changes to build system or external dependencies | None | Modify Webpack config, add npm scripts | +| `revert` | Revert a previous commit | Depends on original | Revert previous commit | diff --git a/docs/development/testing-guide.md b/docs/development/testing-guide.md new file mode 100644 index 0000000..68f16ab --- /dev/null +++ b/docs/development/testing-guide.md @@ -0,0 +1,29 @@ +# Testing Guide + +## Running Tests + +```bash +npm test # Run all tests +npm run test:watch # Run tests in watch mode +npm run test:coverage # Run tests with coverage report +``` + +## Running Specific Tests + +Run a specific test file: + +```bash +npx jest src/utils/foo.test.ts # Run specific test file +npx jest -t "" # Run tests matching pattern +``` + +## Coverage Requirements + +Coverage thresholds are set to 80% for all metrics (branches, functions, lines, statements). CLI code is excluded from coverage as it's verified through integration tests. + +## Writing Tests + +- Test files should be co-located with source files as `**/*.test.ts` +- Use the path alias `@/` which maps to `src/` +- Tests run in Node.js environment using ts-jest preset +- Follow existing test patterns in the codebase diff --git a/docs/user-guide/cli-usage.md b/docs/user-guide/cli-usage.md new file mode 100644 index 0000000..6cb0cff --- /dev/null +++ b/docs/user-guide/cli-usage.md @@ -0,0 +1,91 @@ +# CLI Usage + +## Validate Command + +### Basic Usage + +Validate all CODE_REF references in your documentation: + +```bash +npx coderef validate +``` + +### Validate Specific Files or Directories + +You can specify files or directories to validate: + +```bash +# Validate a specific file +npx coderef validate docs/README.md + +# Validate a specific directory +npx coderef validate docs/backend/ + +# Validate multiple files/directories +npx coderef validate docs/README.md docs/api/ +``` + +### Options + +- `--verbose`, `-v`: Display detailed output including reference counts per file + +```bash +npx coderef validate --verbose +``` + +## Fix Command + +### Basic Usage + +Fix errors interactively with colored diffs: + +```bash +npx coderef fix +``` + +This will prompt you for each error with options to apply the fix or skip. + +### Options + +- `--auto`: Automatically apply all fixes without prompting for confirmation + +```bash +npx coderef fix --auto +``` + +- `--dry-run`: Simulate fixes without making actual changes (useful for testing) + +```bash +npx coderef fix --dry-run +``` + +- `--backup`: Create backup files before applying fixes (default: no backup) + +```bash +npx coderef fix --backup +``` + +- `--verbose`, `-v`: Display detailed output during the fix process + +```bash +npx coderef fix --verbose +``` + +### Combining Options + +You can combine multiple options: + +```bash +# Auto-fix with backup +npx coderef fix --auto --backup + +# Dry run with verbose output +npx coderef fix --dry-run --verbose +``` + +## Exit Codes + +Both commands return appropriate exit codes: + +- `0`: Success (no errors found or all fixes applied) +- `1`: Errors found (validate) or fixes failed (fix) diff --git a/docs/user-guide/code-ref-syntax.md b/docs/user-guide/code-ref-syntax.md new file mode 100644 index 0000000..67728ba --- /dev/null +++ b/docs/user-guide/code-ref-syntax.md @@ -0,0 +1,211 @@ +# CODE_REF Syntax + +## Overview + +CODE_REF comments allow you to reference code in your documentation and keep it synchronized with your actual source code. + +## Reference by Line Numbers + +Reference code by specifying line ranges: + +```markdown + +``` + +This will extract lines 10-20 from `src/index.ts`. + +## Reference by Symbol Name + +Reference code by function or variable name: + +```markdown + +``` + +This will find and extract the `myFunction` function from `src/index.ts` using AST parsing. + +## Reference Class Methods + +Reference specific class methods: + +```markdown + +``` + +This will find and extract the `myMethod` method from the `MyClass` class in `src/MyClass.ts`. + +## Examples + +### Example 1: Line-based Reference + +Suppose you have a file `src/utils/helper.ts` with the following content: + +```typescript +// src/utils/helper.ts +export function greet(name: string): string { + return `Hello, ${name}!`; +} + +export function farewell(name: string): string { + return `Goodbye, ${name}!`; +} +``` + +In your markdown documentation, you can reference the `greet` function by line numbers: + +````markdown +Here's our greeting function: + + + +```typescript +export function greet(name: string): string { + return `Hello, ${name}!`; +} +``` +```` + +The tool will verify that lines 2-4 match the code block below the comment. + +### Example 2: Symbol Reference + +Using the same file, you can reference by function name instead: + +````markdown +Here's our greeting function: + + + +```typescript +export function greet(name: string): string { + return `Hello, ${name}!`; +} +``` +```` + +The tool will use AST parsing to find the `greet` function, regardless of which line it's on. + +### Example 3: Class Method Reference + +Suppose you have a class in `src/models/User.ts`: + +```typescript +// src/models/User.ts +export class User { + constructor(private name: string) {} + + getName(): string { + return this.name; + } + + setName(name: string): void { + this.name = name; + } +} +``` + +You can reference the `getName` method specifically: + +````markdown +The `getName` method returns the user's name: + + + +```typescript +getName(): string { + return this.name; +} +``` +```` + +### Example 4: Multiple References in One Document + +You can have multiple CODE_REF comments in a single markdown file: + +````markdown +# API Documentation + +## Authentication + + + +```typescript +export async function authenticate(username: string, password: string) { + // authentication logic +} +``` + +## User Management + + + +```typescript +getName(): string { + return this.name; +} +``` +```` + +## Best Practices + +- Use line-based references for stable code sections +- Use symbol references for code that may move around in the file +- Keep referenced code blocks focused and readable +- Update references when refactoring code +- Always include the code block immediately after the CODE_REF comment + +## Common Mistakes + +### Missing Code Block + +❌ **Wrong:** + +```markdown + + +Some other content here... +``` + +✅ **Correct:** + +````markdown + + +```typescript +export function myFunction() { + // ... +} +``` +```` + +### Incorrect Path + +❌ **Wrong:** + +```markdown + +``` + +✅ **Correct:** + +```markdown + +``` + +Always use paths relative to your project root. + +## Validation and Fixing + +After adding CODE_REF comments, run validation: + +```bash +npx coderef validate +``` + +If there are mismatches, fix them interactively: + +```bash +npx coderef fix +``` + +For implementation details, see [CODE_REF Syntax Implementation](../architecture/code-ref-syntax.md). diff --git a/docs/user-guide/configuration.md b/docs/user-guide/configuration.md new file mode 100644 index 0000000..b8e29fb --- /dev/null +++ b/docs/user-guide/configuration.md @@ -0,0 +1,284 @@ +# Configuration + +## Configuration File + +Create `.coderefrc.json` in your project root: + +```json +{ + "projectRoot": ".", + "docsDir": "docs", + "ignoreFile": ".docsignore", + "ignorePatterns": ["**/*.draft.md"], + "verbose": false +} +``` + +## Configuration Sources + +The tool loads configuration from multiple sources with the following precedence (highest to lowest): + +1. **Programmatic options** - Passed directly to the API +2. **Environment variables** - `CODEREF_*` prefixed variables +3. **`.coderefrc.json`** - Configuration file in project root +4. **`package.json`** - `"coderef"` field +5. **Default values** - Built-in defaults + +## Configuration Options + +### Base Configuration (CodeRefConfig) + +#### `projectRoot` + +- **Type**: `string` +- **Default**: `process.cwd()` (current working directory) +- **Description**: Root directory of your project. All paths are resolved relative to this directory. + +```json +{ + "projectRoot": "." +} +``` + +#### `docsDir` + +- **Type**: `string` +- **Default**: `"docs"` +- **Description**: Directory containing your documentation files, relative to `projectRoot`. + +```json +{ + "docsDir": "documentation" +} +``` + +#### `ignoreFile` + +- **Type**: `string` (optional) +- **Default**: `".docsignore"` +- **Description**: Path to ignore file relative to `projectRoot`. The file follows `.gitignore` syntax. + +```json +{ + "ignoreFile": ".docsignore" +} +``` + +#### `ignorePatterns` + +- **Type**: `string[]` (optional) +- **Default**: `undefined` +- **Description**: Additional glob patterns to ignore, complementing the ignore file. + +```json +{ + "ignorePatterns": ["**/*.draft.md", "**/temp/**", "**/*.backup.md"] +} +``` + +#### `verbose` + +- **Type**: `boolean` (optional) +- **Default**: `false` +- **Description**: Enable verbose logging for detailed output. + +```json +{ + "verbose": true +} +``` + +#### `targets` + +- **Type**: `string[]` (optional) +- **Default**: `undefined` +- **Description**: Specific files or directories to validate, relative to `docsDir`. If not specified, all markdown files in `docsDir` are validated. + +```json +{ + "targets": ["README.md", "guides/"] +} +``` + +### Fix Command Configuration (CodeRefFixConfig) + +The `fix` command extends the base configuration with additional options: + +#### `dryRun` + +- **Type**: `boolean` (optional) +- **Default**: `false` +- **Description**: Show what would be fixed without modifying files (simulation mode). + +```json +{ + "dryRun": true +} +``` + +#### `auto` + +- **Type**: `boolean` (optional) +- **Default**: `false` +- **Description**: Automatically apply all fixes without prompting for confirmation. + +```json +{ + "auto": true +} +``` + +#### `backup` + +- **Type**: `boolean` (optional) +- **Default**: `true` +- **Description**: Create backup files (`.backup` extension) before applying fixes. + +```json +{ + "backup": false +} +``` + +## Configuration Examples + +### Basic Configuration + +Minimal configuration for a standard project: + +```json +{ + "projectRoot": ".", + "docsDir": "docs" +} +``` + +### Advanced Configuration + +Configuration with custom ignore patterns and verbose output: + +```json +{ + "projectRoot": ".", + "docsDir": "documentation", + "ignoreFile": ".docsignore", + "ignorePatterns": ["**/*.draft.md", "**/archive/**", "**/_*.md"], + "verbose": true +} +``` + +### Monorepo Configuration + +Configuration for a monorepo with multiple documentation directories: + +```json +{ + "projectRoot": "packages/my-package", + "docsDir": "docs", + "ignoreFile": "../../.docsignore" +} +``` + +## Environment Variables + +You can override configuration using environment variables: + +```bash +# Set project root +export CODEREF_PROJECT_ROOT=/path/to/project + +# Set docs directory +export CODEREF_DOCS_DIR=documentation + +# Set ignore file +export CODEREF_IGNORE_FILE=.customignore + +# Enable verbose mode +export CODEREF_VERBOSE=true + +# Run validation +npx coderef validate +``` + +## package.json Configuration + +Alternatively, you can define configuration in `package.json`: + +```json +{ + "name": "my-package", + "version": "1.0.0", + "coderef": { + "docsDir": "documentation", + "verbose": true + } +} +``` + +## Ignore Files + +### .docsignore Syntax + +The `.docsignore` file follows the same syntax as `.gitignore`: + +``` +# Ignore draft files +**/*.draft.md + +# Ignore temporary directories +**/temp/ +**/tmp/ + +# Ignore specific files +notes.md +TODO.md + +# Negative patterns (don't ignore) +!important.draft.md +``` + +### Common Ignore Patterns + +``` +# Build artifacts +**/dist/ +**/build/ + +# Temporary files +**/*.tmp +**/*.backup + +# Version control +**/.git/ + +# Node modules +**/node_modules/ + +# Editor files +**/.vscode/ +**/.idea/ +``` + +## Programmatic Usage + +You can also configure the tool programmatically when using the API: + +```typescript +import { validate, fix } from '@cawpea/coderef'; + +// Validate with custom configuration +await validate({ + projectRoot: '.', + docsDir: 'docs', + verbose: true, + ignorePatterns: ['**/*.draft.md'], +}); + +// Fix with custom configuration +await fix({ + projectRoot: '.', + docsDir: 'docs', + auto: true, + backup: true, +}); +``` diff --git a/docs/user-guide/installation.md b/docs/user-guide/installation.md new file mode 100644 index 0000000..8b6a5c0 --- /dev/null +++ b/docs/user-guide/installation.md @@ -0,0 +1,22 @@ +# Installation + +## Requirements + +- **Minimum Node.js version**: 16.0.0 +- **Recommended version**: See `.node-version` file in the project root + +## Install the Package + +```bash +npm install --save-dev @cawpea/coderef +``` + +You can also use yarn or pnpm: + +```bash +# yarn +yarn add --dev @cawpea/coderef + +# pnpm +pnpm add --save-dev @cawpea/coderef +``` diff --git a/src/index.ts b/src/index.ts index af4e0ed..23399d6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -45,6 +45,7 @@ export type { FixOptions, FixResult, ExpandedMatch, + GitExecOptions, } from '@/utils/types'; // Utility functions From be5b41d41015aa05697099625ad484e441ba2ffe Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Tue, 30 Dec 2025 21:19:16 +0900 Subject: [PATCH 22/57] fix: add pull-requests write permission to claude-code-review workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude Code needs pull-requests: write permission to post review comments to PRs using gh pr comment command. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- .github/workflows/claude-code-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index de529e4..96d2cd5 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest permissions: contents: read - pull-requests: read + pull-requests: write issues: read id-token: write From cd8f000e8213b9731413e4e1ce169a3ff3baa584 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Tue, 30 Dec 2025 21:28:20 +0900 Subject: [PATCH 23/57] chore: remove vscode setting from .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 330c316..d863396 100644 --- a/.gitignore +++ b/.gitignore @@ -125,7 +125,6 @@ dist # Stores VSCode versions used for testing VSCode extensions .vscode/ .vscode-test -!.vscode/settings.json # Claude.ai files .claude/ From 4a20066692f73e453cf806ed0d2dc2ed097bf9cd Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Tue, 30 Dec 2025 21:57:40 +0900 Subject: [PATCH 24/57] feat: translate japanese logs and comments to english --- src/utils/ast-scope-expansion.ts | 4 +- src/utils/code-ellipsis.test.ts | 8 ++-- src/utils/code-ellipsis.ts | 56 +++++++++++++------------- src/utils/diff-display.test.ts | 12 +++--- src/utils/diff-display.ts | 54 ++++++++++++------------- src/utils/markdown-edit.test.ts | 12 +++--- src/utils/markdown-edit.ts | 68 ++++++++++++++++---------------- src/utils/prompt.test.ts | 30 +++++++------- src/utils/prompt.ts | 48 +++++++++++----------- 9 files changed, 146 insertions(+), 146 deletions(-) diff --git a/src/utils/ast-scope-expansion.ts b/src/utils/ast-scope-expansion.ts index f1cb78b..3f4042f 100644 --- a/src/utils/ast-scope-expansion.ts +++ b/src/utils/ast-scope-expansion.ts @@ -268,7 +268,7 @@ function tryASTExpansion( } catch (error) { if (error instanceof Error) { if (error.name === 'SyntaxError' || error.message.includes('Unexpected token')) { - return { success: false, error: `構文エラー: ${error.message}` }; + return { success: false, error: `Syntax error: ${error.message}` }; } return { success: false, error: error.message }; } @@ -293,7 +293,7 @@ export function expandMatchToScope(options: MatchExpansionOptions): ExpandedMatc } // AST解析が失敗した場合、元のマッチを返す(フォールバック) - console.warn(`⚠️ AST解析失敗: ${astResult.error}`); + console.warn(`⚠️ AST parsing failed: ${astResult.error}`); return [ { start: originalMatch.start, diff --git a/src/utils/code-ellipsis.test.ts b/src/utils/code-ellipsis.test.ts index 940d3ed..a3715a1 100644 --- a/src/utils/code-ellipsis.test.ts +++ b/src/utils/code-ellipsis.test.ts @@ -228,19 +228,19 @@ describe('code-ellipsis', () => { it('省略記号を含む行を削除すること', () => { const code = `export class TestClass { - // ... (省略) ... + // ... (omitted) ... targetMethod() { console.log('target'); } - // ... (省略) ... + // ... (omitted) ... }`; const result = removeEllipsis(code); - expect(result).not.toContain('// ... (省略) ...'); + expect(result).not.toContain('// ... (omitted) ...'); expect(result).toContain('targetMethod()'); expect(result).toContain('export class TestClass {'); }); @@ -268,7 +268,7 @@ describe('code-ellipsis', () => { const result = removeEllipsis(code); - expect(result).not.toContain('// ... (省略) ...'); + expect(result).not.toContain('// ... (omitted) ...'); expect(result).toContain('method1()'); expect(result).toContain('method2()'); }); diff --git a/src/utils/code-ellipsis.ts b/src/utils/code-ellipsis.ts index d3ce567..ad39411 100644 --- a/src/utils/code-ellipsis.ts +++ b/src/utils/code-ellipsis.ts @@ -1,5 +1,5 @@ /** - * コード省略表示ユーティリティ + * Code ellipsis display utility */ import * as fs from 'fs'; @@ -9,10 +9,10 @@ import type { TSESTree } from '@typescript-eslint/typescript-estree'; import { isTypeScriptOrJavaScript } from '@/utils/ast-symbol-search'; -const ELLIPSIS = ' // ... (省略) ...'; +const ELLIPSIS = ' // ... (omitted) ...'; /** - * クラスメンバー情報 + * Class member information */ interface ClassMember { name: string; @@ -22,7 +22,7 @@ interface ClassMember { } /** - * ASTからクラスノードを検索 + * Find class node from AST */ function findClassNode(fileContent: string, className: string): TSESTree.ClassDeclaration | null { const ast = parseTypeScript(fileContent, { @@ -36,7 +36,7 @@ function findClassNode(fileContent: string, className: string): TSESTree.ClassDe return node; } - // 子ノードを再帰的に探索 + // Recursively explore child nodes const keys = Object.keys(node) as (keyof TSESTree.Node)[]; for (const key of keys) { const value = node[key]; @@ -62,7 +62,7 @@ function findClassNode(fileContent: string, className: string): TSESTree.ClassDe } /** - * クラス内のメンバーを取得 + * Get members in class */ function getClassMembers(classNode: TSESTree.ClassDeclaration): ClassMember[] { const members: ClassMember[] = []; @@ -104,16 +104,16 @@ function getClassMembers(classNode: TSESTree.ClassDeclaration): ClassMember[] { } /** - * JSDocコメントを含む開始行を取得 + * Get start line including JSDoc comment */ function getStartLineWithJSDoc(startLine: number, lines: string[]): number { - // 開始行の直前から上に向かってJSDocを探す + // Search for JSDoc upward from before start line for (let i = startLine - 2; i >= 0; i--) { const line = lines[i].trim(); - // JSDocの終わり(*/)を見つけた場合 + // If found JSDoc end (*/) if (line.endsWith('*/')) { - // さらに上に向かってJSDocの始まり(/**)を探す + // Search further upward for JSDoc start (/**) for (let j = i; j >= 0; j--) { const docLine = lines[j].trim(); if (docLine.startsWith('/**')) { @@ -123,7 +123,7 @@ function getStartLineWithJSDoc(startLine: number, lines: string[]): number { break; } - // 空行やコメントでない行が見つかったら終了 + // End if found non-empty, non-comment line if (line.length > 0 && !line.startsWith('//')) { break; } @@ -133,7 +133,7 @@ function getStartLineWithJSDoc(startLine: number, lines: string[]): number { } /** - * コードに省略記号を挿入 + * Insert ellipsis in code */ export function insertEllipsis( filePath: string, @@ -142,7 +142,7 @@ export function insertEllipsis( memberName: string; } ): string { - // TypeScript/JavaScriptファイルのみ + // Only TypeScript/JavaScript files if (!isTypeScriptOrJavaScript(filePath)) { throw new Error('TypeScript/JavaScript files only'); } @@ -151,21 +151,21 @@ export function insertEllipsis( const lines = fileContent.split('\n'); if (options.className) { - // クラス内のメソッドのみ表示 + // Display only methods in class const classNode = findClassNode(fileContent, options.className); if (!classNode?.loc) { - // クラスが見つからない場合は元のファイル内容を返す + // If class not found, return original file content return fileContent; } const result: string[] = []; const members = getClassMembers(classNode); - // クラス宣言行を取得(export class ClassName { の行) + // Get class declaration line (export class ClassName { line) const classStartLine = classNode.loc.start.line; const classEndLine = classNode.loc.end.line; - // クラス宣言の開始行を追加 + // Add class declaration start line result.push(lines[classStartLine - 1]); let lastEndLine = classStartLine; @@ -173,10 +173,10 @@ export function insertEllipsis( for (const member of members) { if (member.name === options.memberName) { - // ターゲットメソッド: JSDocコメントを含めて完全に表示 + // Target method: Display fully including JSDoc comment const memberStartWithJSDoc = getStartLineWithJSDoc(member.startLine, lines); - // 前のメンバーとの間に省略記号を挿入(まだ追加していない場合) + // Insert ellipsis between previous member (if not yet added) if (lastEndLine < memberStartWithJSDoc && !hasAddedEllipsis) { result.push(''); result.push(ELLIPSIS); @@ -184,7 +184,7 @@ export function insertEllipsis( hasAddedEllipsis = true; } - // ターゲットメソッドを追加 + // Add target method for (let i = memberStartWithJSDoc - 1; i < member.endLine; i++) { result.push(lines[i]); } @@ -192,7 +192,7 @@ export function insertEllipsis( lastEndLine = member.endLine; hasAddedEllipsis = false; } else { - // 他のメンバー: 省略(省略記号は一度だけ追加) + // Other members: Omit (ellipsis added only once) if (!hasAddedEllipsis && lastEndLine < member.endLine) { hasAddedEllipsis = true; } @@ -200,31 +200,31 @@ export function insertEllipsis( } } - // 最後のメンバーの後に省略記号を追加(必要な場合) + // Add ellipsis after last member (if needed) if (hasAddedEllipsis && lastEndLine < classEndLine - 1) { result.push(''); result.push(ELLIPSIS); result.push(''); } - // クラスの閉じ括弧を追加 + // Add class closing bracket result.push(lines[classEndLine - 1]); return result.join('\n'); } else { - // 関数のみ表示(前後のコンテキストは省略) - // この場合は、シンボル検索で見つかった範囲のみを返す - // 実装が必要な場合は追加 + // Display function only (omit surrounding context) + // In this case, return only the range found by symbol search + // Add if implementation needed return fileContent; } } /** - * 省略表示されたコードから省略記号を除去 + * Remove ellipsis from ellipsis-displayed code */ export function removeEllipsis(code: string): string { return code .split('\n') - .filter((line) => !line.trim().includes('// ... (省略) ...')) + .filter((line) => !line.trim().includes('// ... (omitted) ...')) .join('\n'); } diff --git a/src/utils/diff-display.test.ts b/src/utils/diff-display.test.ts index 4c0ff22..dd84a17 100644 --- a/src/utils/diff-display.test.ts +++ b/src/utils/diff-display.test.ts @@ -6,8 +6,8 @@ describe('displayCodeDiff', () => { const result = displayCodeDiff(code, code); // ヘッダーとフッターが含まれることを確認 - expect(result).toContain('期待されるコード'); - expect(result).toContain('実際のコード'); + expect(result).toContain('Expected code'); + expect(result).toContain('Actual code'); // 差分マーカー(- や +)がないことを確認 expect(result.split('\n').filter((line) => line.startsWith('-')).length).toBe(0); expect(result.split('\n').filter((line) => line.startsWith('+')).length).toBe(0); @@ -55,8 +55,8 @@ describe('displayLineRangeDiff', () => { const result = displayLineRangeDiff(code, expectedRange, actualRange); // ヘッダーが含まれることを確認 - expect(result).toContain('期待される行範囲: 10-11'); - expect(result).toContain('実際の行範囲: 15-16'); + expect(result).toContain('Expected line range: 10-11'); + expect(result).toContain('Actual line range: 15-16'); // 行番号が表示されることを確認(ANSIカラーコードを含む形式) expect(result).toContain('10'); @@ -75,8 +75,8 @@ describe('displayLineRangeDiff', () => { const actualRange = { start: 10, end: 10 }; const result = displayLineRangeDiff(code, expectedRange, actualRange); - expect(result).toContain('期待される行範囲: 5-5'); - expect(result).toContain('実際の行範囲: 10-10'); + expect(result).toContain('Expected line range: 5-5'); + expect(result).toContain('Actual line range: 10-10'); expect(result).toContain('5'); expect(result).toContain('10'); expect(result).toContain('const foo = "bar";'); diff --git a/src/utils/diff-display.ts b/src/utils/diff-display.ts index 816ffd2..f3efcbe 100644 --- a/src/utils/diff-display.ts +++ b/src/utils/diff-display.ts @@ -1,8 +1,8 @@ /** - * コードの差分を視覚的に表示するユーティリティ + * Utility for visually displaying code diffs */ -// ANSIカラーコード +// ANSI color codes const COLORS = { RED: '\x1b[31m', GREEN: '\x1b[32m', @@ -11,10 +11,10 @@ const COLORS = { }; /** - * 2つのコードブロックの差分を行単位で表示 - * @param expected 期待されるコード(ドキュメント内のコードブロック) - * @param actual 実際のコード(ファイルから取得したコード) - * @returns 色付けされた差分表示の文字列 + * Display diff between two code blocks line by line + * @param expected Expected code (code block in document) + * @param actual Actual code (code retrieved from file) + * @returns Colored diff display string */ export function displayCodeDiff(expected: string, actual: string): string { const expectedLines = expected.split('\n'); @@ -23,32 +23,32 @@ export function displayCodeDiff(expected: string, actual: string): string { const output: string[] = []; const maxLines = Math.max(expectedLines.length, actualLines.length); - // ヘッダー + // Header output.push( `${COLORS.DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.RESET}` ); - output.push(`${COLORS.RED}- 期待されるコード (ドキュメント内)${COLORS.RESET}`); - output.push(`${COLORS.GREEN}+ 実際のコード (ファイル内)${COLORS.RESET}`); + output.push(`${COLORS.RED}- Expected code (in document)${COLORS.RESET}`); + output.push(`${COLORS.GREEN}+ Actual code (in file)${COLORS.RESET}`); output.push( `${COLORS.DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.RESET}` ); - // 行ごとに比較 + // Compare line by line for (let i = 0; i < maxLines; i++) { const expectedLine = expectedLines[i]; const actualLine = actualLines[i]; if (expectedLine === actualLine) { - // 一致する行(両方存在する場合) + // Matching lines (when both exist) if (expectedLine !== undefined) { output.push(` ${expectedLine}`); } } else { - // 期待される行(削除された行) + // Expected lines (deleted lines) if (expectedLine !== undefined) { output.push(`${COLORS.RED}- ${expectedLine}${COLORS.RESET}`); } - // 実際の行(追加された行) + // Actual lines (added lines) if (actualLine !== undefined) { output.push(`${COLORS.GREEN}+ ${actualLine}${COLORS.RESET}`); } @@ -63,11 +63,11 @@ export function displayCodeDiff(expected: string, actual: string): string { } /** - * 行番号の差分を視覚的に表示(CODE_LOCATION_MISMATCH用) - * @param code コードの内容 - * @param expectedRange 期待される行範囲 - * @param actualRange 実際の行範囲 - * @returns 色付けされた行番号差分表示の文字列 + * Visually display line number diff (for CODE_LOCATION_MISMATCH) + * @param code Code content + * @param expectedRange Expected line range + * @param actualRange Actual line range + * @returns Colored line number diff display string */ export function displayLineRangeDiff( code: string, @@ -76,27 +76,27 @@ export function displayLineRangeDiff( ): string { const output: string[] = []; - // ヘッダー + // Header output.push( `${COLORS.DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.RESET}` ); output.push( - `${COLORS.RED}- 期待される行範囲: ${expectedRange.start}-${expectedRange.end}${COLORS.RESET}` + `${COLORS.RED}- Expected line range: ${expectedRange.start}-${expectedRange.end}${COLORS.RESET}` ); output.push( - `${COLORS.GREEN}+ 実際の行範囲: ${actualRange.start}-${actualRange.end}${COLORS.RESET}` + `${COLORS.GREEN}+ Actual line range: ${actualRange.start}-${actualRange.end}${COLORS.RESET}` ); output.push( `${COLORS.DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.RESET}` ); - // コードを表示(内容は同じなので、両方の行番号を表示) + // Display code (content is the same, show both line numbers) const codeLines = code.split('\n'); codeLines.forEach((line, index) => { const expectedLineNum = expectedRange.start + index; const actualLineNum = actualRange.start + index; - // 期待される行番号と実際の行番号を並べて表示 + // Display expected and actual line numbers side by side output.push( `${COLORS.RED}${expectedLineNum.toString().padStart(4)}${COLORS.RESET} | ` + `${COLORS.GREEN}${actualLineNum.toString().padStart(4)}${COLORS.RESET} | ` + @@ -112,10 +112,10 @@ export function displayLineRangeDiff( } /** - * 長いテキストを切り詰める(表示用) - * @param text テキスト - * @param maxLength 最大長 - * @returns 切り詰められたテキスト + * Truncate long text (for display) + * @param text Text + * @param maxLength Maximum length + * @returns Truncated text */ export function truncateText(text: string, maxLength: number): string { if (text.length <= maxLength) { diff --git a/src/utils/markdown-edit.test.ts b/src/utils/markdown-edit.test.ts index fbcd1f2..3fa531c 100644 --- a/src/utils/markdown-edit.test.ts +++ b/src/utils/markdown-edit.test.ts @@ -36,7 +36,7 @@ Some text`; '', '' ); - }).toThrow('CODE_REFコメントが見つかりません'); + }).toThrow('CODE_REF comment not found'); }); it('複数のCODE_REFコメントがある場合、最初のマッチを置換すること', () => { @@ -98,7 +98,7 @@ Some text`; '', 'const x = 1;' ); - }).toThrow('CODE_REFコメントが見つかりません'); + }).toThrow('CODE_REF comment not found'); }); it('CODE_REFコメントの終了タグがない場合、エラーをスローすること', () => { @@ -106,7 +106,7 @@ Some text`; expect(() => { insertCodeBlockAfterComment(content, '', 10); - }).toThrow('CODE_REFコメントが見つかりません'); + }).toThrow('CODE_REF comment not found'); }); it('CODE_REFコメントの終了タグがない場合、エラーをスローすること', () => { @@ -299,7 +299,7 @@ const x = 1; expect(() => { moveCodeRefCommentBeforeCodeBlock(content, ')を検索 + // Search for comment end (-->) const commentEnd = content.indexOf('-->', commentIndex); if (commentEnd === -1) { - throw new Error('CODE_REFコメントの終了タグが見つかりません'); + throw new Error('CODE_REF comment end tag not found'); } - // コメント後に挿入、次の行にスキップ + // Insert after comment, skip to next line const insertPosition = commentEnd + 3; - // コメント後に既にコンテンツがあるかチェック + // Check if content already exists after comment const afterComment = content.substring(insertPosition, insertPosition + 10); const newlinePrefix = afterComment.startsWith('\n') ? '\n' : '\n\n'; @@ -54,14 +54,14 @@ export function insertCodeBlockAfterComment( } /** - * マークダウン内のコードブロックを置換 + * Replace code block in markdown */ export function replaceCodeBlock( content: string, oldCodeBlock: string, newCodeBlock: string ): string { - // マークダウン形式でコードブロックを検索 + // Search for code block in markdown format const codeBlockPattern = /```[\w]*\n([\s\S]*?)```/g; let match: RegExpExecArray | null; let found = false; @@ -69,13 +69,13 @@ export function replaceCodeBlock( while ((match = codeBlockPattern.exec(content)) !== null) { const blockContent = match[1]; - // 正規化して比較 + // Normalize and compare if (normalizeCode(blockContent) === normalizeCode(oldCodeBlock)) { - // このブロックを置換 + // Replace this block const startIndex = match.index; const endIndex = startIndex + match[0].length; - // 言語識別子を抽出 + // Extract language identifier const langMatch = /```([\w]*)\n/.exec(match[0]); const language = langMatch ? langMatch[1] : ''; @@ -88,14 +88,14 @@ export function replaceCodeBlock( } if (!found) { - throw new Error('一致するコードブロックが見つかりません'); + throw new Error('No matching code block found'); } return content; } /** - * CODE_REFコメント後のコードブロック位置を検索 + * Find code block position after CODE_REF comment */ export function findCodeBlockPosition( content: string, @@ -111,7 +111,7 @@ export function findCodeBlockPosition( return null; } - // コメント後500文字以内でコードブロックを検索 + // Search for code block within 500 characters after comment const searchStart = commentEnd + 3; const searchWindow = content.substring(searchStart, searchStart + 500); @@ -128,62 +128,62 @@ export function findCodeBlockPosition( } /** - * CODE_REFコメントをコードブロックの直前に移動 + * Move CODE_REF comment before code block * - * @param content マークダウンファイルの内容 - * @param commentMatch CODE_REFコメントのテキスト - * @param codeBlockStart コードブロックの開始位置 - * @returns 修正後のマークダウン内容 + * @param content Markdown file content + * @param commentMatch CODE_REF comment text + * @param codeBlockStart Code block start position + * @returns Modified markdown content */ export function moveCodeRefCommentBeforeCodeBlock( content: string, commentMatch: string, codeBlockStart: number ): string { - // 1. CODE_REFコメントの位置を特定 + // 1. Identify CODE_REF comment position const commentIndex = content.indexOf(commentMatch); if (commentIndex === -1) { - throw new Error(`CODE_REFコメントが見つかりません: ${commentMatch}`); + throw new Error(`CODE_REF comment not found: ${commentMatch}`); } const commentEnd = content.indexOf('-->', commentIndex); if (commentEnd === -1) { - throw new Error('CODE_REFコメントの終了タグが見つかりません'); + throw new Error('CODE_REF comment end tag not found'); } - // 2. 削除範囲を決定(前後の改行を含める) + // 2. Determine removal range (including surrounding newlines) let removalStart = commentIndex; let removalEnd = commentEnd + 3; - // コメントの前に改行がある場合、それも削除 + // If newline exists before comment, remove it too if (commentIndex > 0 && content[commentIndex - 1] === '\n') { removalStart = commentIndex - 1; } - // コメントの後の改行を削除(最大2つ) + // Remove newlines after comment (max 2) let newlinesAfter = 0; while (removalEnd < content.length && content[removalEnd] === '\n' && newlinesAfter < 2) { removalEnd++; newlinesAfter++; } - // 3. コメントを削除 + // 3. Remove comment const contentWithoutComment = content.substring(0, removalStart) + content.substring(removalEnd); - // 4. 位置調整(削除によるシフト) + // 4. Adjust position (shift due to removal) const removedLength = removalEnd - removalStart; const adjustedCodeBlockStart = codeBlockStart > commentIndex ? codeBlockStart - removedLength : codeBlockStart; - // 5. コードブロックの直前に挿入 + // 5. Insert before code block const beforeCodeBlock = contentWithoutComment.substring(0, adjustedCodeBlockStart); const afterCodeBlock = contentWithoutComment.substring(adjustedCodeBlockStart); - // コメントの前に適切な改行を追加 + // Add appropriate newline before comment const needsNewlineBefore = beforeCodeBlock.length > 0 && !beforeCodeBlock.endsWith('\n\n'); const prefix = needsNewlineBefore ? '\n' : ''; - // コメントとコードブロックの間に1つの改行 + // One newline between comment and code block const commentWithNewline = `${prefix}${commentMatch}\n`; return beforeCodeBlock + commentWithNewline + afterCodeBlock; diff --git a/src/utils/prompt.test.ts b/src/utils/prompt.test.ts index 4e43c5b..6273190 100644 --- a/src/utils/prompt.test.ts +++ b/src/utils/prompt.test.ts @@ -180,7 +180,7 @@ describe('prompt', () => { const result = await askSelectOption(mockRl, options, 'Choose:'); expect(result).toBe(0); - expect(consoleLogSpy).toHaveBeenCalledWith('❌ 無効な選択です。もう一度入力してください。'); + expect(consoleLogSpy).toHaveBeenCalledWith('❌ Invalid selection. Please try again.'); consoleLogSpy.mockRestore(); }); @@ -202,7 +202,7 @@ describe('prompt', () => { const result = await askSelectOption(mockRl, options, 'Choose:'); expect(result).toBe(0); - expect(consoleLogSpy).toHaveBeenCalledWith('❌ 無効な選択です。もう一度入力してください。'); + expect(consoleLogSpy).toHaveBeenCalledWith('❌ Invalid selection. Please try again.'); consoleLogSpy.mockRestore(); }); @@ -241,8 +241,8 @@ describe('prompt', () => { displayFixPreview(action); - expect(consoleLogSpy).toHaveBeenCalledWith('\n変更内容:'); - expect(consoleLogSpy).toHaveBeenCalledWith('- 説明: Update line numbers'); + expect(consoleLogSpy).toHaveBeenCalledWith('\nChanges:'); + expect(consoleLogSpy).toHaveBeenCalledWith('- Description: Update line numbers'); }); it('INSERT_CODE_BLOCKタイプの修正プレビューを表示すること', () => { @@ -266,9 +266,9 @@ describe('prompt', () => { displayFixPreview(action); - expect(consoleLogSpy).toHaveBeenCalledWith('\n変更内容:'); - expect(consoleLogSpy).toHaveBeenCalledWith('- 説明: Insert code block'); - expect(consoleLogSpy).toHaveBeenCalledWith('\x1b[32m+ コードブロックを挿入:\x1b[0m'); + expect(consoleLogSpy).toHaveBeenCalledWith('\nChanges:'); + expect(consoleLogSpy).toHaveBeenCalledWith('- Description: Insert code block'); + expect(consoleLogSpy).toHaveBeenCalledWith('\x1b[32m+ Insert code block:\x1b[0m'); }); it('CODE_CONTENT_MISMATCHタイプの修正プレビューを表示すること', () => { @@ -294,8 +294,8 @@ describe('prompt', () => { displayFixPreview(action); - expect(consoleLogSpy).toHaveBeenCalledWith('\n変更内容:'); - expect(consoleLogSpy).toHaveBeenCalledWith('- 説明: Replace code block'); + expect(consoleLogSpy).toHaveBeenCalledWith('\nChanges:'); + expect(consoleLogSpy).toHaveBeenCalledWith('- Description: Replace code block'); }); it('CODE_LOCATION_MISMATCHタイプの修正プレビューを表示すること(コードブロックなし)', () => { @@ -318,8 +318,8 @@ describe('prompt', () => { displayFixPreview(action); - expect(consoleLogSpy).toHaveBeenCalledWith('\n変更内容:'); - expect(consoleLogSpy).toHaveBeenCalledWith('- 説明: Update line numbers'); + expect(consoleLogSpy).toHaveBeenCalledWith('\nChanges:'); + expect(consoleLogSpy).toHaveBeenCalledWith('- Description: Update line numbers'); expect(consoleLogSpy).toHaveBeenCalledWith('Simple preview text'); }); @@ -346,8 +346,8 @@ describe('prompt', () => { displayFixPreview(action); - expect(consoleLogSpy).toHaveBeenCalledWith('\n変更内容:'); - expect(consoleLogSpy).toHaveBeenCalledWith('- 説明: Replace code block'); + expect(consoleLogSpy).toHaveBeenCalledWith('\nChanges:'); + expect(consoleLogSpy).toHaveBeenCalledWith('- Description: Replace code block'); }); it('UPDATE_END_LINEタイプの修正プレビューを表示すること', () => { @@ -375,8 +375,8 @@ describe('prompt', () => { displayFixPreview(action); - expect(consoleLogSpy).toHaveBeenCalledWith('\n変更内容:'); - expect(consoleLogSpy).toHaveBeenCalledWith('- 説明: Update end line number'); + expect(consoleLogSpy).toHaveBeenCalledWith('\nChanges:'); + expect(consoleLogSpy).toHaveBeenCalledWith('- Description: Update end line number'); }); }); }); diff --git a/src/utils/prompt.ts b/src/utils/prompt.ts index 91ce440..c6cd3f1 100644 --- a/src/utils/prompt.ts +++ b/src/utils/prompt.ts @@ -1,5 +1,5 @@ /** - * 対話的CLIプロンプトユーティリティ + * Interactive CLI prompt utility */ import * as fs from 'fs'; @@ -11,7 +11,7 @@ import { displayCodeDiff, displayLineRangeDiff } from '@/utils/diff-display'; import type { FixAction } from '@/utils/types'; /** - * Readlineインターフェースを作成 + * Create readline interface */ export function createPromptInterface(): readline.Interface { return readline.createInterface({ @@ -21,7 +21,7 @@ export function createPromptInterface(): readline.Interface { } /** - * Yes/No質問 + * Yes/No question */ export async function askYesNo( rl: readline.Interface, @@ -39,7 +39,7 @@ export async function askYesNo( } /** - * 質問して回答を取得 + * Ask question and get answer */ export function askQuestion(rl: readline.Interface, question: string): Promise { return new Promise((resolve) => { @@ -50,8 +50,8 @@ export function askQuestion(rl: readline.Interface, question: string): Promise= 1 && selection <= options.length) { return selection - 1; } - console.log('❌ 無効な選択です。もう一度入力してください。'); + console.log('❌ Invalid selection. Please try again.'); } } /** - * 修正プレビューを表示 + * Display fix preview */ export function displayFixPreview(action: FixAction): void { - console.log('\n変更内容:'); - console.log(`- 説明: ${action.description}`); + console.log('\nChanges:'); + console.log(`- Description: ${action.description}`); - // エラータイプに応じて色付き差分を表示 + // Display colored diff based on error type const { error } = action; const projectRoot = path.resolve(__dirname, '../../..'); const absolutePath = path.resolve(projectRoot, error.ref.refPath); switch (error.type) { case 'CODE_LOCATION_MISMATCH': { - // 行番号の差分を表示 + // Display line number diff if (error.ref.codeBlock && action.newStartLine && action.newEndLine) { - // コードブロックが存在する場合、実際のコードを取得 + // If code block exists, get actual code const actualCode = extractLinesFromFile( absolutePath, action.newStartLine, @@ -111,14 +111,14 @@ export function displayFixPreview(action: FixAction): void { ); console.log(diff); } else { - // コードブロックがない場合はシンプルなプレビュー + // Simple preview if no code block console.log(action.preview); } break; } case 'CODE_CONTENT_MISMATCH': { - // コード内容の差分を表示 + // Display code content diff if (error.expectedCode && action.newCodeBlock) { const diff = displayCodeDiff(error.expectedCode, action.newCodeBlock); console.log(diff); @@ -129,9 +129,9 @@ export function displayFixPreview(action: FixAction): void { } case 'INSERT_CODE_BLOCK': { - // 新規挿入の場合、挿入されるコードをシンプルに表示 + // For new insertion, simply display the code to be inserted if (action.newCodeBlock) { - console.log('\x1b[32m+ コードブロックを挿入:\x1b[0m'); + console.log('\x1b[32m+ Insert code block:\x1b[0m'); console.log('\x1b[2m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m'); const lines = action.newCodeBlock.split('\n'); lines.forEach((line) => { @@ -145,7 +145,7 @@ export function displayFixPreview(action: FixAction): void { } case 'REPLACE_CODE_BLOCK': { - // コードブロックの置換の場合、CODE_CONTENT_MISMATCHと同じ処理 + // For code block replacement, same processing as CODE_CONTENT_MISMATCH if (error.expectedCode && action.newCodeBlock) { const diff = displayCodeDiff(error.expectedCode, action.newCodeBlock); console.log(diff); @@ -157,7 +157,7 @@ export function displayFixPreview(action: FixAction): void { case 'UPDATE_LINE_NUMBERS': case 'UPDATE_END_LINE': { - // 行番号更新の場合、コメントの変更を表示 + // For line number update, display comment change const oldComment = error.ref.fullMatch; const newComment = ``; @@ -166,10 +166,10 @@ export function displayFixPreview(action: FixAction): void { console.log(`\x1b[32m+ ${newComment}\x1b[0m`); console.log('\x1b[2m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m'); - // コードブロックも存在する場合は内容も表示 + // If code block also exists, display content if (action.newCodeBlock && fs.existsSync(absolutePath)) { - console.log('\nコードブロック内容:'); - const lines = action.newCodeBlock.split('\n').slice(0, 10); // 最初の10行 + console.log('\nCode block content:'); + const lines = action.newCodeBlock.split('\n').slice(0, 10); // First 10 lines lines.forEach((line) => { console.log(` ${line}`); }); @@ -181,7 +181,7 @@ export function displayFixPreview(action: FixAction): void { } default: { - // その他の場合は従来のプレビューを表示 + // For other cases, display default preview if (action.preview) { console.log(action.preview); } From dc923c00358a6407b1fa78cf92e37c3404ced4ec Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Tue, 30 Dec 2025 22:43:55 +0900 Subject: [PATCH 25/57] ci: add release workflow usign semantic-release --- .github/workflows/release.yml | 59 + .releaserc.json | 23 + docs/README.md | 1 + docs/development/release.md | 388 + package-lock.json | 12077 ++++++++++++++++++++++++-------- package.json | 10 + 6 files changed, 9749 insertions(+), 2809 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 .releaserc.json create mode 100644 docs/development/release.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d5e027b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,59 @@ +name: Release + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: write + issues: write + pull-requests: write + id-token: write + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22.x + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build package + run: npm run build + + - name: Run tests + run: npm test + + - name: Run linter + run: npm run lint + + - name: Run type check + run: npm run type-check + + - name: Verify build artifacts + run: | + if [ ! -d "dist" ]; then + echo "Build failed: dist directory not found" + exit 1 + fi + echo "Build artifacts verified successfully" + + - name: Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + run: npx semantic-release diff --git a/.releaserc.json b/.releaserc.json new file mode 100644 index 0000000..c03be81 --- /dev/null +++ b/.releaserc.json @@ -0,0 +1,23 @@ +{ + "branches": ["main"], + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + [ + "@semantic-release/changelog", + { + "changelogFile": "CHANGELOG.md", + "changelogTitle": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)." + } + ], + "@semantic-release/npm", + [ + "@semantic-release/git", + { + "assets": ["CHANGELOG.md", "package.json", "package-lock.json"], + "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" + } + ], + "@semantic-release/github" + ] +} diff --git a/docs/README.md b/docs/README.md index 1137d0b..7b903df 100644 --- a/docs/README.md +++ b/docs/README.md @@ -19,6 +19,7 @@ Start here if you want to contribute: - [Coding Standards](development/coding-standards.md) - Style guide and conventions - [Git Conventions](development/git-conventions.md) - Commit message format - [Testing Guide](development/testing-guide.md) - Writing and running tests +- [Release Process](development/release.md) - Release procedures and automation ## Architecture & Design diff --git a/docs/development/release.md b/docs/development/release.md new file mode 100644 index 0000000..1ab3c6c --- /dev/null +++ b/docs/development/release.md @@ -0,0 +1,388 @@ +# Release Process + +This document describes the release process for `@cawpea/coderef`. + +## Overview + +The release process is automated using [semantic-release](https://semantic-release.gitbook.io/). It automatically determines versions based on Conventional Commits, generates CHANGELOGs, publishes to npm, and creates GitHub releases. + +## Prerequisites + +### Initial Setup (Repository Administrators Only) + +To enable release automation, the following configuration is required: + +1. **Create NPM Token** + - Visit https://www.npmjs.com/settings/YOUR_USERNAME/tokens + - Click "Generate New Token" + - Token type: **Automation** + - Scope: Publish access to `@cawpea` scope + +2. **Configure GitHub Secrets** + - Go to GitHub repository Settings → Secrets and variables → Actions + - Click "New repository secret" + - Name: `NPM_TOKEN` + - Value: Paste the npm token created above + +### Developer Prerequisites + +- Understanding of [Conventional Commits](https://www.conventionalcommits.org/) specification +- Familiarity with project [Git Conventions](./git-conventions.md) + +## Release Methods + +### Method 1: Automatic Release (Recommended) + +Releases are automatically triggered when merging to the `main` branch. + +#### Steps + +1. **Develop on a Feature Branch** + + ```bash + git checkout -b feature/your-feature + # Develop and commit + ``` + +2. **Follow Conventional Commits** + + ```bash + # For new features (MINOR version bump) + git commit -m "feat: add new validation feature" + + # For bug fixes (PATCH version bump) + git commit -m "fix: correct reference parsing" + + # For breaking changes (MAJOR version bump) + git commit -m "feat!: change API interface + + BREAKING CHANGE: API signature has changed" + ``` + +3. **Create Pull Request for Review** + + ```bash + git push origin feature/your-feature + # Create PR on GitHub + ``` + +4. **Merge to main Branch** + - After PR is reviewed and approved, merge to `main` branch + - Release workflow will automatically start after merge + +5. **Verify Release Completion** + - Check Release workflow execution in GitHub Actions + - Verify new version is published on npm + - Verify new release is created on GitHub Releases + +### Method 2: Manual Release + +You can manually trigger a release from the GitHub UI when needed. + +#### Steps + +1. **Open GitHub Repository Page** + - https://github.com/cawpea/coderef + +2. **Navigate to Actions Tab** + - Click the "Actions" tab at the top + +3. **Select Release Workflow** + - Select "Release" workflow from the left sidebar + +4. **Run Workflow** + - Click the "Run workflow" button in the top right + - Select branch (usually `main`) + - Click "Run workflow" + +5. **Verify Release Completion** + - Check workflow execution logs + - Verify on npm and GitHub Releases + +**Note**: Due to semantic-release configuration, releases will only be created from the `main` branch. + +## Release Process Details + +### Executed Operations + +When the Release workflow runs, the following operations are executed sequentially: + +1. **Checkout Code** + - Fetch complete Git history (`fetch-depth: 0`) + +2. **Quality Checks** + - Build (`npm run build`) + - Tests (`npm test`) + - Lint (`npm run lint`) + - Type check (`npm run type-check`) + +3. **Run semantic-release** + - Commit analysis (analyze commits since last release) + - Version determination (based on commit types) + - Update CHANGELOG.md + - Update package.json version + - Publish package to npm + - Create GitHub release + - Commit changes and create tag + +### Version Determination Rules + +Versions are automatically determined based on commit messages: + +| Commit Type | Example | Version Change | +| ------------------------------ | ----------------------- | --------------------- | +| `feat:` | `feat: add new feature` | MINOR (0.1.0 → 0.2.0) | +| `fix:` | `fix: resolve bug` | PATCH (0.1.0 → 0.1.1) | +| `BREAKING CHANGE:` | In footer | MAJOR (0.1.0 → 1.0.0) | +| `feat!:` or `fix!:` | `feat!: change API` | MAJOR (0.1.0 → 1.0.0) | +| `docs:`, `chore:`, `ci:`, etc. | - | No release | + +See [Git Conventions](./git-conventions.md) for more details. + +### Generated Artifacts + +When a release succeeds, the following are automatically created: + +1. **npm Package** + - https://www.npmjs.com/package/@cawpea/coderef + - New version is published + +2. **Git Tag** + - Format: `vX.Y.Z` (e.g., `v0.2.0`) + - Created on `main` branch + +3. **GitHub Release** + - Auto-generated release notes + - Changelog based on commits + +4. **CHANGELOG.md** + - New version entry is added + - Maintains Keep a Changelog format + +5. **Version Bump Commit** + - Commit message: `chore(release): X.Y.Z [skip ci]` + - `[skip ci]` prevents workflow from re-running on this commit + +## Verifying Releases + +After a release, verify the following: + +### 1. Check GitHub Actions + +- Visit https://github.com/cawpea/coderef/actions +- Verify Release workflow completed successfully +- Ensure all steps have green checkmarks + +### 2. Check npm Package + +```bash +# Check latest version +npm view @cawpea/coderef version + +# Test package installation +npm install @cawpea/coderef@latest +``` + +### 3. Check GitHub Release + +- Visit https://github.com/cawpea/coderef/releases +- Verify new release was created +- Verify release notes are correctly generated + +### 4. Check CHANGELOG.md + +- Review CHANGELOG.md in the repository +- Verify new version entry was added +- Verify changes are correctly documented + +### 5. Check Git Tags + +```bash +# List all tags +git tag -l + +# Check latest tag +git describe --tags --abbrev=0 +``` + +## Troubleshooting + +### Release Not Triggered + +**Symptom**: No release is created after merging to `main` branch + +**Causes and Solutions**: + +1. **No Releasable Commits** + - If only `docs:`, `chore:`, `ci:` commits exist, no version bump occurs + - Check semantic-release logs: "no release" message indicates this is normal + +2. **Quality Check Failure** + - Release is aborted if build, tests, lint, or type check fails + - Check GitHub Actions logs for errors and fix them + +3. **NPM_TOKEN Misconfiguration** + - Verify `NPM_TOKEN` is correctly set in GitHub Secrets + - Verify token has appropriate permissions (Automation token, Publish access) + +### npm Publication Failure + +**Symptom**: Release workflow succeeds but package is not published to npm + +**Causes and Solutions**: + +1. **Authentication Error** + - Check NPM_TOKEN expiration + - Regenerate token and update GitHub Secrets + +2. **Package Name Conflict** + - Verify package with same name doesn't already exist on npm + - For scoped packages (`@cawpea/coderef`), verify `publishConfig.access: "public"` is set + +3. **Network Error** + - May be a temporary error + - Manually re-run the workflow + +### GitHub Release Creation Failure + +**Symptom**: Package is published to npm but GitHub Release is not created + +**Causes and Solutions**: + +1. **Insufficient Permissions** + - Check `permissions` settings in Release workflow + - Verify `contents: write` is set + +2. **GITHUB_TOKEN Issue** + - Verify `GITHUB_TOKEN` is correctly set in workflow + - If `persist-credentials: false`, additional token configuration may be needed + +### Manual Execution Doesn't Release + +**Symptom**: Manual execution shows "no release" + +**Causes and Solutions**: + +1. **Branch Verification** + - Verify you're not running from a branch other than `main` + - Check `branches` configuration in `.releaserc.json` + +2. **Commit History Verification** + - Verify there are releasable commits (`feat:`, `fix:`, etc.) since last release + +## Rollback Procedures + +Procedures for rolling back a release when issues occur. + +### Unpublish npm Package + +**Warning**: npm allows unpublish within 72 hours of publication. + +```bash +# Unpublish package version (within 72 hours) +npm unpublish @cawpea/coderef@X.Y.Z + +# Or deprecate package (after 72 hours) +npm deprecate @cawpea/coderef@X.Y.Z "This version has critical issues. Please use vX.Y.Z-1 instead." +``` + +### Delete GitHub Release + +1. **Delete from GitHub UI** + - Visit https://github.com/cawpea/coderef/releases + - Open the relevant release + - Click "Delete" button + +2. **Delete from CLI** + ```bash + gh release delete vX.Y.Z + ``` + +### Delete Git Tag + +```bash +# Delete local tag +git tag -d vX.Y.Z + +# Delete remote tag +git push --delete origin vX.Y.Z +``` + +### Revert Release Commit + +```bash +# Switch to main branch +git checkout main +git pull origin main + +# Identify release commit +git log --oneline | grep "chore(release)" + +# Revert release commit (undo with new commit) +git revert + +# Push to remote +git push origin main +``` + +**Warning**: Use `git revert` to preserve history while undoing changes. Avoid `git reset --hard` on shared repositories as it rewrites history. + +### Emergency Patch Release + +Steps to release a fixed version after rolling back a problematic release: + +1. **Create Fix Branch** + + ```bash + git checkout -b hotfix/critical-fix + ``` + +2. **Fix the Issue** + + ```bash + # Make fixes + git add . + git commit -m "fix: resolve critical issue in vX.Y.Z" + ``` + +3. **Create PR and Merge** + - Follow normal release process + - PATCH version will automatically increment due to `fix:` commit + +## Best Practices + +### Commit Messages + +- Strictly follow Conventional Commits specification +- Use scopes to clarify where changes were made + ```bash + feat(cli): add --verbose option + fix(parser): handle edge case in CODE_REF + ``` + +### Pre-Release Verification + +- Test thoroughly locally +- Verify CI passes before merging +- Update documentation for breaking changes + +### Post-Release Verification + +- Verify publication on npm package page +- Review release notes on GitHub Releases +- Manually supplement release notes if needed + +### Version Management + +- Follow Semantic Versioning +- Before 1.0.0 (0.x.x), MINOR versions may include breaking changes +- From 1.0.0 onwards, breaking changes must increment MAJOR version + +## References + +- [semantic-release Documentation](https://semantic-release.gitbook.io/) +- [Conventional Commits](https://www.conventionalcommits.org/) +- [Semantic Versioning](https://semver.org/) +- [Keep a Changelog](https://keepachangelog.com/) +- [Git Conventions](./git-conventions.md) +- [npm Documentation](https://docs.npmjs.com/) diff --git a/package-lock.json b/package-lock.json index 5903b47..00ec170 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,12 @@ "coderef": "bin/coderef.js" }, "devDependencies": { + "@semantic-release/changelog": "^6.0.3", + "@semantic-release/commit-analyzer": "^11.1.0", + "@semantic-release/git": "^10.0.1", + "@semantic-release/github": "^9.2.6", + "@semantic-release/npm": "^11.0.2", + "@semantic-release/release-notes-generator": "^12.1.0", "@types/jest": "^30.0.0", "@types/node": "^24.10.1", "@typescript-eslint/eslint-plugin": "^8.18.1", @@ -26,6 +32,7 @@ "jest": "^30.2.0", "lint-staged": "^16.2.7", "prettier": "^3.4.2", + "semantic-release": "^23.0.0", "ts-jest": "^29.4.5", "tsup": "^8.0.0", "typescript": "^5.9.3", @@ -551,6 +558,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/@emnapi/core": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", @@ -1808,6 +1826,235 @@ "@tybys/wasm-util": "^0.10.0" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@octokit/auth-token": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", + "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.2.tgz", + "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^4.0.0", + "@octokit/graphql": "^7.1.0", + "@octokit/request": "^8.4.1", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/endpoint": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz", + "integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz", + "integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request": "^8.4.1", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.2.tgz", + "integrity": "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.6.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, + "node_modules/@octokit/plugin-retry": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-6.1.0.tgz", + "integrity": "sha512-WrO3bvq4E1Xh1r2mT9w6SDFg01gFmP81nIG77+p/MqW1JeXXgL++6umim3t6x0Zj5pZm3rXAN+0HEjmmdhIRig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^13.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-throttling": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-8.2.0.tgz", + "integrity": "sha512-nOpWtLayKFpgqmgD0y3GqXafMFuKcA4tRPZIfu7BArd2lEZeb1988nhWhwx4aZWmjDmUfdgVf7W+Tt4AmvRmMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.2.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "^5.0.0" + } + }, + "node_modules/@octokit/plugin-throttling/node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-throttling/node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, + "node_modules/@octokit/request": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz", + "integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^9.0.6", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz", + "integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -1832,6 +2079,51 @@ "url": "https://opencollective.com/pkgr" } }, + "node_modules/@pnpm/config.env-replace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", + "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "4.2.10" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true, + "license": "ISC" + }, + "node_modules/@pnpm/npm-conf": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.3.1.tgz", + "integrity": "sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pnpm/config.env-replace": "^1.1.0", + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.54.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz", @@ -2140,3341 +2432,8939 @@ "win32" ] }, - "node_modules/@sinclair/typebox": { - "version": "0.34.45", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.45.tgz", - "integrity": "sha512-qJcFVfCa5jxBFSuv7S5WYbA8XdeCPmhnaVVfX/2Y6L8WYg8sk3XY2+6W0zH+3mq1Cz+YC7Ki66HfqX6IHAwnkg==", + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", "dev": true, "license": "MIT" }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "node_modules/@semantic-release/changelog": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@semantic-release/changelog/-/changelog-6.0.3.tgz", + "integrity": "sha512-dZuR5qByyfe3Y03TpmCvAxCyTnp7r5XwtHRf/8vD9EAn4ZWbavUX8adMtXYzE86EVh0gyLA7lm5yW4IV30XUag==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "type-detect": "4.0.8" + "@semantic-release/error": "^3.0.0", + "aggregate-error": "^3.0.0", + "fs-extra": "^11.0.0", + "lodash": "^4.17.4" + }, + "engines": { + "node": ">=14.17" + }, + "peerDependencies": { + "semantic-release": ">=18.0.0" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "13.0.5", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", - "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "node_modules/@semantic-release/commit-analyzer": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-11.1.0.tgz", + "integrity": "sha512-cXNTbv3nXR2hlzHjAMgbuiQVtvWHTlwwISt60B+4NZv01y/QRY7p2HcJm8Eh2StzcTJoNnflvKjHH/cjFS7d5g==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "@sinonjs/commons": "^3.0.1" + "conventional-changelog-angular": "^7.0.0", + "conventional-commits-filter": "^4.0.0", + "conventional-commits-parser": "^5.0.0", + "debug": "^4.0.0", + "import-from-esm": "^1.0.3", + "lodash-es": "^4.17.21", + "micromatch": "^4.0.2" + }, + "engines": { + "node": "^18.17 || >=20.6.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" } }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "node_modules/@semantic-release/error": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-3.0.0.tgz", + "integrity": "sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw==", "dev": true, "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" + "engines": { + "node": ">=14.17" } }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "node_modules/@semantic-release/git": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/git/-/git-10.0.1.tgz", + "integrity": "sha512-eWrx5KguUcU2wUPaO6sfvZI0wPafUKAMNC18aXY4EnNcrZL86dEmpNVnC9uMpGZkmZJ9EfCVJBQx4pV4EMGT1w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" + "@semantic-release/error": "^3.0.0", + "aggregate-error": "^3.0.0", + "debug": "^4.0.0", + "dir-glob": "^3.0.0", + "execa": "^5.0.0", + "lodash": "^4.17.4", + "micromatch": "^4.0.0", + "p-reduce": "^2.0.0" + }, + "engines": { + "node": ">=14.17" + }, + "peerDependencies": { + "semantic-release": ">=18.0.0" } }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "node_modules/@semantic-release/github": { + "version": "9.2.6", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-9.2.6.tgz", + "integrity": "sha512-shi+Lrf6exeNZF+sBhK+P011LSbhmIAoUEgEY6SsxF8irJ+J2stwI5jkyDQ+4gzYyDImzV6LCKdYB9FXnQRWKA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.0.0" + "@octokit/core": "^5.0.0", + "@octokit/plugin-paginate-rest": "^9.0.0", + "@octokit/plugin-retry": "^6.0.0", + "@octokit/plugin-throttling": "^8.0.0", + "@semantic-release/error": "^4.0.0", + "aggregate-error": "^5.0.0", + "debug": "^4.3.4", + "dir-glob": "^3.0.1", + "globby": "^14.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "issue-parser": "^6.0.0", + "lodash-es": "^4.17.21", + "mime": "^4.0.0", + "p-filter": "^4.0.0", + "url-join": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" } }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "node_modules/@semantic-release/github/node_modules/@semantic-release/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" + "engines": { + "node": ">=18" } }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "node_modules/@semantic-release/github/node_modules/aggregate-error": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", + "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.2" + "clean-stack": "^5.2.0", + "indent-string": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "node_modules/@semantic-release/github/node_modules/clean-stack": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.3.0.tgz", + "integrity": "sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg==", "dev": true, "license": "MIT", "dependencies": { - "@types/istanbul-lib-coverage": "*" + "escape-string-regexp": "5.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "node_modules/@semantic-release/github/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "dev": true, "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@types/jest": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", - "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "node_modules/@semantic-release/github/node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", "dev": true, "license": "MIT", - "dependencies": { - "expect": "^30.0.0", - "pretty-format": "^30.0.0" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "24.10.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.4.tgz", - "integrity": "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==", + "node_modules/@semantic-release/npm": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-11.0.3.tgz", + "integrity": "sha512-KUsozQGhRBAnoVg4UMZj9ep436VEGwT536/jwSqB7vcEfA6oncCUU7UIYTRdLx7GvTtqn0kBjnkfLVkcnBa2YQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "@semantic-release/error": "^4.0.0", + "aggregate-error": "^5.0.0", + "execa": "^8.0.0", + "fs-extra": "^11.0.0", + "lodash-es": "^4.17.21", + "nerf-dart": "^1.0.0", + "normalize-url": "^8.0.0", + "npm": "^10.5.0", + "rc": "^1.2.8", + "read-pkg": "^9.0.0", + "registry-auth-token": "^5.0.0", + "semver": "^7.1.2", + "tempy": "^3.0.0" + }, + "engines": { + "node": "^18.17 || >=20" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" } }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "node_modules/@semantic-release/npm/node_modules/@semantic-release/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", "dev": true, "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" + "engines": { + "node": ">=18" } }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.1.tgz", - "integrity": "sha512-PKhLGDq3JAg0Jk/aK890knnqduuI/Qj+udH7wCf0217IGi4gt+acgCyPVe79qoT+qKUvHMDQkwJeKW9fwl8Cyw==", + "node_modules/@semantic-release/npm/node_modules/aggregate-error": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", + "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.50.1", - "@typescript-eslint/type-utils": "8.50.1", - "@typescript-eslint/utils": "8.50.1", - "@typescript-eslint/visitor-keys": "8.50.1", - "ignore": "^7.0.0", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" + "clean-stack": "^5.2.0", + "indent-string": "^5.0.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.50.1", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@typescript-eslint/parser": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.1.tgz", - "integrity": "sha512-hM5faZwg7aVNa819m/5r7D0h0c9yC4DUlWAOvHAtISdFTc8xB86VmX5Xqabrama3wIPJ/q9RbGS1worb6JfnMg==", + "node_modules/@semantic-release/npm/node_modules/clean-stack": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.3.0.tgz", + "integrity": "sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.50.1", - "@typescript-eslint/types": "8.50.1", - "@typescript-eslint/typescript-estree": "8.50.1", - "@typescript-eslint/visitor-keys": "8.50.1", - "debug": "^4.3.4" + "escape-string-regexp": "5.0.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=14.16" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.1.tgz", - "integrity": "sha512-E1ur1MCVf+YiP89+o4Les/oBAVzmSbeRB0MQLfSlYtbWU17HPxZ6Bhs5iYmKZRALvEuBoXIZMOIRRc/P++Ortg==", + "node_modules/@semantic-release/npm/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.50.1", - "@typescript-eslint/types": "^8.50.1", - "debug": "^4.3.4" - }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.1.tgz", - "integrity": "sha512-mfRx06Myt3T4vuoHaKi8ZWNTPdzKPNBhiblze5N50//TSHOAQQevl/aolqA/BcqqbJ88GUnLqjjcBc8EWdBcVw==", + "node_modules/@semantic-release/npm/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.50.1", - "@typescript-eslint/visitor-keys": "8.50.1" + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=16.17" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.1.tgz", - "integrity": "sha512-ooHmotT/lCWLXi55G4mvaUF60aJa012QzvLK0Y+Mp4WdSt17QhMhWOaBWeGTFVkb2gDgBe19Cxy1elPXylslDw==", + "node_modules/@semantic-release/npm/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=16" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.50.1.tgz", - "integrity": "sha512-7J3bf022QZE42tYMO6SL+6lTPKFk/WphhRPe9Tw/el+cEwzLz1Jjz2PX3GtGQVxooLDKeMVmMt7fWpYRdG5Etg==", + "node_modules/@semantic-release/npm/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/@semantic-release/npm/node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", "dev": true, "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.50.1", - "@typescript-eslint/typescript-estree": "8.50.1", - "@typescript-eslint/utils": "8.50.1", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" - }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@typescript-eslint/types": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.1.tgz", - "integrity": "sha512-v5lFIS2feTkNyMhd7AucE/9j/4V9v5iIbpVRncjk/K0sQ6Sb+Np9fgYS/63n6nwqahHQvbmujeBL7mp07Q9mlA==", + "node_modules/@semantic-release/npm/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.1.tgz", - "integrity": "sha512-woHPdW+0gj53aM+cxchymJCrh0cyS7BTIdcDxWUNsclr9VDkOSbqC13juHzxOmQ22dDkMZEpZB+3X1WpUvzgVQ==", + "node_modules/@semantic-release/npm/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.50.1", - "@typescript-eslint/tsconfig-utils": "8.50.1", - "@typescript-eslint/types": "8.50.1", - "@typescript-eslint/visitor-keys": "8.50.1", - "debug": "^4.3.4", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.1.0" - }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@typescript-eslint/utils": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.1.tgz", - "integrity": "sha512-lCLp8H1T9T7gPbEuJSnHwnSuO9mDf8mfK/Nion5mZmiEaQD9sWf9W4dfeFqRyqRjF06/kBuTmAqcs9sewM2NbQ==", + "node_modules/@semantic-release/npm/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.50.1", - "@typescript-eslint/types": "8.50.1", - "@typescript-eslint/typescript-estree": "8.50.1" + "path-key": "^4.0.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.1.tgz", - "integrity": "sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==", + "node_modules/@semantic-release/npm/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.50.1", - "eslint-visitor-keys": "^4.2.1" + "mimic-fn": "^4.0.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "license": "Apache-2.0", + "node_modules/@semantic-release/npm/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=12" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, - "license": "ISC" - }, - "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", - "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", - "cpu": [ - "arm" - ], + "node_modules/@semantic-release/npm/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", - "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", - "cpu": [ - "arm64" - ], + "node_modules/@semantic-release/release-notes-generator": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-12.1.0.tgz", + "integrity": "sha512-g6M9AjUKAZUZnxaJZnouNBeDNTCUrJ5Ltj+VJ60gJeDaRRahcHsry9HW8yKrnKkKNkx5lbWiEP1FPMqVNQz8Kg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "dependencies": { + "conventional-changelog-angular": "^7.0.0", + "conventional-changelog-writer": "^7.0.0", + "conventional-commits-filter": "^4.0.0", + "conventional-commits-parser": "^5.0.0", + "debug": "^4.0.0", + "get-stream": "^7.0.0", + "import-from-esm": "^1.0.3", + "into-stream": "^7.0.0", + "lodash-es": "^4.17.21", + "read-pkg-up": "^11.0.0" + }, + "engines": { + "node": "^18.17 || >=20.6.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" + } }, - "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", - "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", - "cpu": [ - "arm64" - ], + "node_modules/@semantic-release/release-notes-generator/node_modules/get-stream": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-7.0.1.tgz", + "integrity": "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", - "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", - "cpu": [ - "x64" - ], + "node_modules/@sinclair/typebox": { + "version": "0.34.45", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.45.tgz", + "integrity": "sha512-qJcFVfCa5jxBFSuv7S5WYbA8XdeCPmhnaVVfX/2Y6L8WYg8sk3XY2+6W0zH+3mq1Cz+YC7Ki66HfqX6IHAwnkg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "license": "MIT" }, - "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", - "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", - "cpu": [ - "x64" - ], + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } }, - "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", - "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", - "cpu": [ - "arm" - ], + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", - "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", - "cpu": [ - "arm" - ], + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } }, - "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", - "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", - "cpu": [ - "arm64" - ], + "node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } }, - "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", - "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", - "cpu": [ - "arm64" - ], + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "tslib": "^2.4.0" + } }, - "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", - "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", - "cpu": [ - "ppc64" - ], + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } }, - "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", - "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", - "cpu": [ - "riscv64" - ], + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@babel/types": "^7.0.0" + } }, - "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", - "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", - "cpu": [ - "riscv64" - ], + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } }, - "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", - "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", - "cpu": [ - "s390x" - ], + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.10.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.4.tgz", + "integrity": "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.1.tgz", + "integrity": "sha512-PKhLGDq3JAg0Jk/aK890knnqduuI/Qj+udH7wCf0217IGi4gt+acgCyPVe79qoT+qKUvHMDQkwJeKW9fwl8Cyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.50.1", + "@typescript-eslint/type-utils": "8.50.1", + "@typescript-eslint/utils": "8.50.1", + "@typescript-eslint/visitor-keys": "8.50.1", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.50.1", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.1.tgz", + "integrity": "sha512-hM5faZwg7aVNa819m/5r7D0h0c9yC4DUlWAOvHAtISdFTc8xB86VmX5Xqabrama3wIPJ/q9RbGS1worb6JfnMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.50.1", + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/typescript-estree": "8.50.1", + "@typescript-eslint/visitor-keys": "8.50.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.1.tgz", + "integrity": "sha512-E1ur1MCVf+YiP89+o4Les/oBAVzmSbeRB0MQLfSlYtbWU17HPxZ6Bhs5iYmKZRALvEuBoXIZMOIRRc/P++Ortg==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.50.1", + "@typescript-eslint/types": "^8.50.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.1.tgz", + "integrity": "sha512-mfRx06Myt3T4vuoHaKi8ZWNTPdzKPNBhiblze5N50//TSHOAQQevl/aolqA/BcqqbJ88GUnLqjjcBc8EWdBcVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/visitor-keys": "8.50.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.1.tgz", + "integrity": "sha512-ooHmotT/lCWLXi55G4mvaUF60aJa012QzvLK0Y+Mp4WdSt17QhMhWOaBWeGTFVkb2gDgBe19Cxy1elPXylslDw==", + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.50.1.tgz", + "integrity": "sha512-7J3bf022QZE42tYMO6SL+6lTPKFk/WphhRPe9Tw/el+cEwzLz1Jjz2PX3GtGQVxooLDKeMVmMt7fWpYRdG5Etg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/typescript-estree": "8.50.1", + "@typescript-eslint/utils": "8.50.1", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.1.tgz", + "integrity": "sha512-v5lFIS2feTkNyMhd7AucE/9j/4V9v5iIbpVRncjk/K0sQ6Sb+Np9fgYS/63n6nwqahHQvbmujeBL7mp07Q9mlA==", + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.1.tgz", + "integrity": "sha512-woHPdW+0gj53aM+cxchymJCrh0cyS7BTIdcDxWUNsclr9VDkOSbqC13juHzxOmQ22dDkMZEpZB+3X1WpUvzgVQ==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.50.1", + "@typescript-eslint/tsconfig-utils": "8.50.1", + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/visitor-keys": "8.50.1", + "debug": "^4.3.4", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.1.tgz", + "integrity": "sha512-lCLp8H1T9T7gPbEuJSnHwnSuO9mDf8mfK/Nion5mZmiEaQD9sWf9W4dfeFqRyqRjF06/kBuTmAqcs9sewM2NbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.50.1", + "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/typescript-estree": "8.50.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.50.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.1.tgz", + "integrity": "sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.50.1", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/argv-formatter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz", + "integrity": "sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "dev": true, + "license": "MIT" + }, + "node_modules/babel-jest": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", + "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "30.2.0", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.2.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "dev": true, + "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", + "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/babel__core": "^7.20.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", + "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.11", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", + "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bundle-require": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", + "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.18" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001761", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001761.tgz", + "integrity": "sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.1.tgz", + "integrity": "sha512-+CmxIZ/L2vNcEfvNtLdU0ZQ6mbq3FZnwAP2PPTiKP+1QOoKwlKlPgb8UKV0Dds7QVaMnHm+FwSft2VB0s/SLjQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "dev": true, + "license": "ISC", + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/cli-highlight/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-highlight/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cli-highlight/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-highlight/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-highlight/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-highlight/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/cli-highlight/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-highlight/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-table3/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-table3/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-table3/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-table3/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-truncate": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", + "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^7.1.0", + "string-width": "^8.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/conventional-changelog-angular": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", + "integrity": "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-changelog-writer": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-7.0.1.tgz", + "integrity": "sha512-Uo+R9neH3r/foIvQ0MKcsXkX642hdm9odUp7TqgFS7BsalTcjzRlIfWZrZR1gbxOozKucaKt5KAbjW8J8xRSmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "conventional-commits-filter": "^4.0.0", + "handlebars": "^4.7.7", + "json-stringify-safe": "^5.0.1", + "meow": "^12.0.1", + "semver": "^7.5.2", + "split2": "^4.0.0" + }, + "bin": { + "conventional-changelog-writer": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-commits-filter": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-4.0.0.tgz", + "integrity": "sha512-rnpnibcSOdFcdclpFwWa+pPlZJhXE7l+XK04zxhbWrhgpR96h33QLz8hITTXbcYICxVr3HZFtbtUAQ+4LdBo9A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-commits-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", + "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-text-path": "^2.0.0", + "JSONStream": "^1.3.5", + "meow": "^12.0.1", + "split2": "^4.0.0" + }, + "bin": { + "conventional-commits-parser": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/convert-hrtime": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", + "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/crypto-random-string/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", + "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/emojilib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/env-ci": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-11.2.0.tgz", + "integrity": "sha512-D5kWfzkmaOQDioPmiviWAVtKmpPT4/iJmMVQxWxMPJTFyTkdc5JQUfc5iXEeWxcOdsYTKSAiA/Age4NUOqKsRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^8.0.0", + "java-properties": "^1.0.2" + }, + "engines": { + "node": "^18.17 || >=20.6.1" + } + }, + "node_modules/env-ci/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/env-ci/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/env-ci/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/env-ci/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/env-ci/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/env-ci/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/env-ci/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/env-ci/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/env-ci/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.2.tgz", + "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true, + "license": "MIT" + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/exit-x": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.2.0", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/fast-content-type-parse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", + "integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up-simple": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-versions": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-6.0.0.tgz", + "integrity": "sha512-2kCCtc+JvcZ86IGAz3Z2Y0A1baIz9fL31pH/0S1IqZr9Iwnjq8izfPtrCyQKO6TLMPELLsQMre7VDqeIKCsHkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver-regex": "^4.0.5", + "super-regex": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fix-dts-default-cjs-exports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", + "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "rollup": "^4.34.8" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "node_modules/fs-extra": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", + "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-timeout": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-1.0.2.tgz", + "integrity": "sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/git-log-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.1.tgz", + "integrity": "sha512-PI+sPDvHXNPl5WNOErAK05s3j0lgwUzMN6o8cyQrDaKfT3qd7TmNJKeXX+SknI5I0QhG5fVPAEwSY4tRGDtYoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "argv-formatter": "~1.0.0", + "spawn-error-forwarder": "~1.0.0", + "split2": "~1.0.0", + "stream-combiner2": "~1.1.1", + "through2": "~2.0.0", + "traverse": "0.6.8" + } + }, + "node_modules/git-log-parser/node_modules/split2": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", + "integrity": "sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==", + "dev": true, + "license": "ISC", + "dependencies": { + "through2": "~2.0.0" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/hook-std": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-3.0.0.tgz", + "integrity": "sha512-jHRQzjSDzMtFy34AGj1DN+vq54WVuhSvKgrHf0OMiFQTwDD4L/qqofVEWjLOBMTn5+lCD3fPg32W9yOfnEJTTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-from-esm": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/import-from-esm/-/import-from-esm-1.3.4.tgz", + "integrity": "sha512-7EyUlPFC0HOlBDpUFGfYstsU7XHxZJKAAMzCT8wZ0hMW7b+hG51LIKTDcsgtz8Pu6YC0HqRVbX+rVUtsGMUKvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "import-meta-resolve": "^4.0.0" + }, + "engines": { + "node": ">=16.20" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/index-to-position": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", + "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/into-stream": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-7.0.0.tgz", + "integrity": "sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "from2": "^2.3.0", + "p-is-promise": "^3.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-text-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz", + "integrity": "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "text-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/issue-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-6.0.0.tgz", + "integrity": "sha512-zKa/Dxq2lGsBIXQ7CUZWTHfvxPC2ej0KfO7fIPqLlHB9J2hJ7rGhZ5rilhuufylr4RXYPzJUeFjKxz305OsNlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.capitalize": "^4.2.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.uniqby": "^4.7.0" + }, + "engines": { + "node": ">=10.13" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/java-properties": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz", + "integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/jest": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", + "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "30.2.0", + "@jest/types": "30.2.0", + "import-local": "^3.2.0", + "jest-cli": "30.2.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz", + "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.1.1", + "jest-util": "30.2.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", + "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "co": "^4.6.0", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "p-limit": "^3.1.0", + "pretty-format": "30.2.0", + "pure-rand": "^7.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-cli": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz", + "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "yargs": "^17.7.2" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", + "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/get-type": "30.1.0", + "@jest/pattern": "30.0.1", + "@jest/test-sequencer": "30.2.0", + "@jest/types": "30.2.0", + "babel-jest": "30.2.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "deepmerge": "^4.3.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "jest-circus": "30.2.0", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-runner": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "micromatch": "^4.0.8", + "parse-json": "^5.2.0", + "pretty-format": "30.2.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "esbuild-register": ">=3.4.0", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "esbuild-register": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", + "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.0.1", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", + "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", + "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "jest-util": "30.2.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", + "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "jest-mock": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", + "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", + "micromatch": "^4.0.8", + "walker": "^1.0.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" + } + }, + "node_modules/jest-leak-detector": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", + "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", + "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.2.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", + "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.2.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "micromatch": "^4.0.8", + "pretty-format": "30.2.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", + "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "jest-util": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", + "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-pnp-resolver": "^1.2.3", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "slash": "^3.0.0", + "unrs-resolver": "^1.7.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz", + "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "30.0.1", + "jest-snapshot": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", + "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.2.0", + "@jest/environment": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-leak-detector": "30.2.0", + "jest-message-util": "30.2.0", + "jest-resolve": "30.2.0", + "jest-runtime": "30.2.0", + "jest-util": "30.2.0", + "jest-watcher": "30.2.0", + "jest-worker": "30.2.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", + "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/globals": "30.2.0", + "@jest/source-map": "30.0.1", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "cjs-module-lexer": "^2.1.0", + "collect-v8-coverage": "^1.0.2", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", + "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/types": "^7.27.3", + "@jest/expect-utils": "30.2.0", + "@jest/get-type": "30.1.0", + "@jest/snapshot-utils": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0", + "chalk": "^4.1.2", + "expect": "30.2.0", + "graceful-fs": "^4.2.11", + "jest-diff": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "pretty-format": "30.2.0", + "semver": "^7.7.2", + "synckit": "^0.11.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", + "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/jest-validate": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", + "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.2.0", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", + "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "jest-util": "30.2.0", + "string-length": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", + "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.2.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lint-staged": { + "version": "16.2.7", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.7.tgz", + "integrity": "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^14.0.2", + "listr2": "^9.0.5", + "micromatch": "^4.0.8", + "nano-spawn": "^2.0.0", + "pidtree": "^0.6.0", + "string-argv": "^0.3.2", + "yaml": "^2.8.1" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/listr2": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^5.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/listr2/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.22", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.22.tgz", + "integrity": "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.capitalize": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", + "integrity": "sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.uniqby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-escapes": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", + "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/make-asynchronous": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-asynchronous/-/make-asynchronous-1.0.1.tgz", + "integrity": "sha512-T9BPOmEOhp6SmV25SwLVcHK4E6JyG/coH3C6F1NjNXSziv/fd4GmsqMk8YR6qpPOswfaOCApSNkZv6fxoaYFcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-event": "^6.0.0", + "type-fest": "^4.6.0", + "web-worker": "1.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-asynchronous/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/marked": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", + "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/marked-terminal": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.3.0.tgz", + "integrity": "sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "ansi-regex": "^6.1.0", + "chalk": "^5.4.1", + "cli-highlight": "^2.1.11", + "cli-table3": "^0.6.5", + "node-emoji": "^2.2.0", + "supports-hyperlinks": "^3.1.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "marked": ">=1 <16" + } + }, + "node_modules/marked-terminal/node_modules/ansi-escapes": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", + "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/marked-terminal/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/meow": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", + "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz", + "integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa" + ], + "license": "MIT", + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nano-spawn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz", + "integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nerf-dart": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", + "integrity": "sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-emoji": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", + "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.6.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.0.tgz", + "integrity": "sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/npm/-/npm-10.9.4.tgz", + "integrity": "sha512-OnUG836FwboQIbqtefDNlyR0gTHzIfwRfE3DuiNewBvnMnWEpB0VEXwBlFVgqpNzIgYo/MHh3d2Hel/pszapAA==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/config", + "@npmcli/fs", + "@npmcli/map-workspaces", + "@npmcli/package-json", + "@npmcli/promise-spawn", + "@npmcli/redact", + "@npmcli/run-script", + "@sigstore/tuf", + "abbrev", + "archy", + "cacache", + "chalk", + "ci-info", + "cli-columns", + "fastest-levenshtein", + "fs-minipass", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmhook", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minimatch", + "minipass", + "minipass-pipeline", + "ms", + "node-gyp", + "nopt", + "normalize-package-data", + "npm-audit-report", + "npm-install-checks", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "p-map", + "pacote", + "parse-conflict-json", + "proc-log", + "qrcode-terminal", + "read", + "semver", + "spdx-expression-parse", + "ssri", + "supports-color", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which", + "write-file-atomic" + ], + "dev": true, + "license": "Artistic-2.0", + "workspaces": [ + "docs", + "smoke-tests", + "mock-globals", + "mock-registry", + "workspaces/*" + ], + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^8.0.1", + "@npmcli/config": "^9.0.0", + "@npmcli/fs": "^4.0.0", + "@npmcli/map-workspaces": "^4.0.2", + "@npmcli/package-json": "^6.2.0", + "@npmcli/promise-spawn": "^8.0.2", + "@npmcli/redact": "^3.2.2", + "@npmcli/run-script": "^9.1.0", + "@sigstore/tuf": "^3.1.1", + "abbrev": "^3.0.1", + "archy": "~1.0.0", + "cacache": "^19.0.1", + "chalk": "^5.4.1", + "ci-info": "^4.2.0", + "cli-columns": "^4.0.0", + "fastest-levenshtein": "^1.0.16", + "fs-minipass": "^3.0.3", + "glob": "^10.4.5", + "graceful-fs": "^4.2.11", + "hosted-git-info": "^8.1.0", + "ini": "^5.0.0", + "init-package-json": "^7.0.2", + "is-cidr": "^5.1.1", + "json-parse-even-better-errors": "^4.0.0", + "libnpmaccess": "^9.0.0", + "libnpmdiff": "^7.0.1", + "libnpmexec": "^9.0.1", + "libnpmfund": "^6.0.1", + "libnpmhook": "^11.0.0", + "libnpmorg": "^7.0.0", + "libnpmpack": "^8.0.1", + "libnpmpublish": "^10.0.1", + "libnpmsearch": "^8.0.0", + "libnpmteam": "^7.0.0", + "libnpmversion": "^7.0.0", + "make-fetch-happen": "^14.0.3", + "minimatch": "^9.0.5", + "minipass": "^7.1.1", + "minipass-pipeline": "^1.2.4", + "ms": "^2.1.2", + "node-gyp": "^11.2.0", + "nopt": "^8.1.0", + "normalize-package-data": "^7.0.0", + "npm-audit-report": "^6.0.0", + "npm-install-checks": "^7.1.1", + "npm-package-arg": "^12.0.2", + "npm-pick-manifest": "^10.0.0", + "npm-profile": "^11.0.1", + "npm-registry-fetch": "^18.0.2", + "npm-user-validate": "^3.0.0", + "p-map": "^7.0.3", + "pacote": "^19.0.1", + "parse-conflict-json": "^4.0.0", + "proc-log": "^5.0.0", + "qrcode-terminal": "^0.12.0", + "read": "^4.1.0", + "semver": "^7.7.2", + "spdx-expression-parse": "^4.0.0", + "ssri": "^12.0.0", + "supports-color": "^9.4.0", + "tar": "^6.2.1", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^3.0.0", + "validate-npm-package-name": "^6.0.1", + "which": "^5.0.0", + "write-file-atomic": "^6.0.0" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/npm/node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/agent": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/arborist": { + "version": "8.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^4.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/map-workspaces": "^4.0.1", + "@npmcli/metavuln-calculator": "^8.0.0", + "@npmcli/name-from-folder": "^3.0.0", + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.1", + "@npmcli/query": "^4.0.0", + "@npmcli/redact": "^3.0.0", + "@npmcli/run-script": "^9.0.1", + "bin-links": "^5.0.0", + "cacache": "^19.0.1", + "common-ancestor-path": "^1.0.1", + "hosted-git-info": "^8.0.0", + "json-parse-even-better-errors": "^4.0.0", + "json-stringify-nice": "^1.1.4", + "lru-cache": "^10.2.2", + "minimatch": "^9.0.4", + "nopt": "^8.0.0", + "npm-install-checks": "^7.1.0", + "npm-package-arg": "^12.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.1", + "pacote": "^19.0.0", + "parse-conflict-json": "^4.0.0", + "proc-log": "^5.0.0", + "proggy": "^3.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "read-package-json-fast": "^4.0.0", + "semver": "^7.3.7", + "ssri": "^12.0.0", + "treeverse": "^3.0.0", + "walk-up-path": "^3.0.1" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/config": { + "version": "9.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^4.0.1", + "@npmcli/package-json": "^6.0.1", + "ci-info": "^4.0.0", + "ini": "^5.0.0", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/fs": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/git": { + "version": "6.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^8.0.0", + "ini": "^5.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^10.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/installed-package-contents": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/map-workspaces": { + "version": "4.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { + "version": "8.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cacache": "^19.0.0", + "json-parse-even-better-errors": "^4.0.0", + "pacote": "^20.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator/node_modules/pacote": { + "version": "20.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^9.0.0", + "cacache": "^19.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^12.0.0", + "npm-packlist": "^9.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^3.0.0", + "ssri": "^12.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/name-from-folder": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/node-gyp": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/package-json": { + "version": "6.2.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^8.0.0", + "json-parse-even-better-errors": "^4.0.0", + "proc-log": "^5.0.0", + "semver": "^7.5.3", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/promise-spawn": { + "version": "8.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/query": { + "version": "4.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/redact": { + "version": "3.2.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/run-script": { + "version": "9.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "node-gyp": "^11.0.0", + "proc-log": "^5.0.0", + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "dev": true, + "inBundle": true, + "license": "MIT", "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/@sigstore/protobuf-specs": { + "version": "0.4.3", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@sigstore/tuf": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.4.1", + "tuf-js": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/abbrev": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/agent-base": { + "version": "7.1.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-styles": { + "version": "6.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/aproba": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/archy": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/bin-links": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^7.0.0", + "npm-normalize-package-bin": "^4.0.0", + "proc-log": "^5.0.0", + "read-cmd-shim": "^5.0.0", + "write-file-atomic": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", - "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", - "cpu": [ - "x64" - ], + "node_modules/npm/node_modules/binary-extensions": { + "version": "2.3.0", "dev": true, + "inBundle": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", - "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", - "cpu": [ - "x64" - ], + "node_modules/npm/node_modules/brace-expansion": { + "version": "2.0.2", "dev": true, + "inBundle": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "balanced-match": "^1.0.0" + } }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", - "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", - "cpu": [ - "wasm32" - ], + "node_modules/npm/node_modules/cacache": { + "version": "19.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^4.0.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/chownr": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/mkdirp": { + "version": "3.0.1", "dev": true, + "inBundle": true, "license": "MIT", - "optional": true, + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/tar": { + "version": "7.4.3", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.11" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, - "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", - "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", - "cpu": [ - "arm64" - ], + "node_modules/npm/node_modules/cacache/node_modules/yallist": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/chalk": { + "version": "5.4.1", "dev": true, + "inBundle": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } }, - "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", - "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", - "cpu": [ - "ia32" + "node_modules/npm/node_modules/chownr": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ci-info": { + "version": "4.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } ], + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/cidr-regex": { + "version": "4.1.3", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "ip-regex": "^5.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/cli-columns": { + "version": "4.0.0", "dev": true, + "inBundle": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">= 10" + } }, - "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", - "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", - "cpu": [ - "x64" - ], + "node_modules/npm/node_modules/cmd-shim": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/npm/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/common-ancestor-path": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/cross-spawn": { + "version": "7.0.6", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cssesc": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/debug": { + "version": "4.4.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/diff": { + "version": "5.2.0", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/npm/node_modules/eastasianwidth": { + "version": "0.2.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/encoding": { + "version": "0.1.13", "dev": true, + "inBundle": true, "license": "MIT", "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/npm/node_modules/env-paths": { + "version": "2.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/err-code": { + "version": "2.0.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/exponential-backoff": { + "version": "3.1.2", + "dev": true, + "inBundle": true, + "license": "Apache-2.0" }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "node_modules/npm/node_modules/fastest-levenshtein": { + "version": "1.0.16", "dev": true, + "inBundle": true, "license": "MIT", - "bin": { - "acorn": "bin/acorn" + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/npm/node_modules/foreground-child": { + "version": "3.3.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" }, "engines": { - "node": ">=0.4.0" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "node_modules/npm/node_modules/fs-minipass": { + "version": "3.0.3", "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/npm/node_modules/glob": { + "version": "10.4.5", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "node_modules/npm/node_modules/graceful-fs": { + "version": "4.2.11", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/hosted-git-info": { + "version": "8.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "type-fest": "^0.21.3" + "lru-cache": "^10.0.1" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "node_modules/npm/node_modules/http-cache-semantics": { + "version": "4.2.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/http-proxy-agent": { + "version": "7.0.2", "dev": true, + "inBundle": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "engines": { + "node": ">= 14" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/npm/node_modules/https-proxy-agent": { + "version": "7.0.6", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "agent-base": "^7.1.2", + "debug": "4" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">= 14" } }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "node_modules/npm/node_modules/iconv-lite": { + "version": "0.6.3", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "node_modules/npm/node_modules/ignore-walk": { + "version": "7.0.0", "dev": true, + "inBundle": true, "license": "ISC", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "minimatch": "^9.0.0" }, "engines": { - "node": ">= 8" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "node_modules/npm/node_modules/imurmurhash": { + "version": "0.1.4", "dev": true, - "license": "Python-2.0" + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } }, - "node_modules/babel-jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", - "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", + "node_modules/npm/node_modules/ini": { + "version": "5.0.0", "dev": true, - "license": "MIT", - "dependencies": { - "@jest/transform": "30.2.0", - "@types/babel__core": "^7.20.5", - "babel-plugin-istanbul": "^7.0.1", - "babel-preset-jest": "30.2.0", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "slash": "^3.0.0" - }, + "inBundle": true, + "license": "ISC", "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/babel-plugin-istanbul": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", - "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "node_modules/npm/node_modules/init-package-json": { + "version": "7.0.2", "dev": true, - "license": "BSD-3-Clause", - "workspaces": [ - "test/babel-8" - ], + "inBundle": true, + "license": "ISC", "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-instrument": "^6.0.2", - "test-exclude": "^6.0.0" + "@npmcli/package-json": "^6.0.0", + "npm-package-arg": "^12.0.0", + "promzard": "^2.0.0", + "read": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "^6.0.0" }, "engines": { - "node": ">=12" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/babel-plugin-jest-hoist": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", - "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", + "node_modules/npm/node_modules/ip-address": { + "version": "9.0.5", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "@types/babel__core": "^7.20.5" + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 12" } }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", - "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "node_modules/npm/node_modules/ip-regex": { + "version": "5.0.0", "dev": true, + "inBundle": true, "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/is-cidr": { + "version": "5.1.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" + "cidr-regex": "^4.1.1" }, - "peerDependencies": { - "@babel/core": "^7.0.0 || ^8.0.0-0" + "engines": { + "node": ">=14" } }, - "node_modules/babel-preset-jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", - "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", + "node_modules/npm/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", "dev": true, + "inBundle": true, "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/jackspeak": { + "version": "3.4.3", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", "dependencies": { - "babel-plugin-jest-hoist": "30.2.0", - "babel-preset-current-node-syntax": "^1.2.0" + "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "funding": { + "url": "https://github.com/sponsors/isaacs" }, - "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-beta.1" + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "node_modules/npm/node_modules/jsbn": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/json-parse-even-better-errors": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/json-stringify-nice": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/jsonparse": { + "version": "1.3.1", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff": { + "version": "6.0.2", + "dev": true, + "inBundle": true, "license": "MIT" }, - "node_modules/baseline-browser-mapping": { - "version": "2.9.11", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", - "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", + "node_modules/npm/node_modules/just-diff-apply": { + "version": "5.5.0", "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } + "inBundle": true, + "license": "MIT" }, - "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "license": "MIT", + "node_modules/npm/node_modules/libnpmaccess": { + "version": "9.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "balanced-match": "^1.0.0" + "npm-package-arg": "^12.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "node_modules/npm/node_modules/libnpmdiff": { + "version": "7.0.1", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "fill-range": "^7.1.1" + "@npmcli/arborist": "^8.0.1", + "@npmcli/installed-package-contents": "^3.0.0", + "binary-extensions": "^2.3.0", + "diff": "^5.1.0", + "minimatch": "^9.0.4", + "npm-package-arg": "^12.0.0", + "pacote": "^19.0.0", + "tar": "^6.2.1" }, "engines": { - "node": ">=8" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "node_modules/npm/node_modules/libnpmexec": { + "version": "9.0.1", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" + "@npmcli/arborist": "^8.0.1", + "@npmcli/run-script": "^9.0.1", + "ci-info": "^4.0.0", + "npm-package-arg": "^12.0.0", + "pacote": "^19.0.0", + "proc-log": "^5.0.0", + "read": "^4.0.0", + "read-package-json-fast": "^4.0.0", + "semver": "^7.3.7", + "walk-up-path": "^3.0.1" }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "node_modules/npm/node_modules/libnpmfund": { + "version": "6.0.1", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "fast-json-stable-stringify": "2.x" + "@npmcli/arborist": "^8.0.1" }, "engines": { - "node": ">= 6" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "node_modules/npm/node_modules/libnpmhook": { + "version": "11.0.0", "dev": true, - "license": "Apache-2.0", + "inBundle": true, + "license": "ISC", "dependencies": { - "node-int64": "^0.4.0" + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/bundle-require": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", - "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", + "node_modules/npm/node_modules/libnpmorg": { + "version": "7.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "load-tsconfig": "^0.2.3" + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "peerDependencies": { - "esbuild": ">=0.18" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "node_modules/npm/node_modules/libnpmpack": { + "version": "8.0.1", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^8.0.1", + "@npmcli/run-script": "^9.0.1", + "npm-package-arg": "^12.0.0", + "pacote": "^19.0.0" + }, "engines": { - "node": ">=8" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "node_modules/npm/node_modules/libnpmpublish": { + "version": "10.0.1", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", + "dependencies": { + "ci-info": "^4.0.0", + "normalize-package-data": "^7.0.0", + "npm-package-arg": "^12.0.0", + "npm-registry-fetch": "^18.0.1", + "proc-log": "^5.0.0", + "semver": "^7.3.7", + "sigstore": "^3.0.0", + "ssri": "^12.0.0" + }, "engines": { - "node": ">=6" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "node_modules/npm/node_modules/libnpmsearch": { + "version": "8.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^18.0.1" + }, "engines": { - "node": ">=6" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001761", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001761.tgz", - "integrity": "sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/npm/node_modules/libnpmteam": { + "version": "7.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "node_modules/npm/node_modules/libnpmversion": { + "version": "7.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.1", + "@npmcli/run-script": "^9.0.1", + "json-parse-even-better-errors": "^4.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.7" + }, "engines": { - "node": ">=10" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "node_modules/npm/node_modules/lru-cache": { + "version": "10.4.3", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/make-fetch-happen": { + "version": "14.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "readdirp": "^4.0.1" + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" }, "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/ci-info": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", - "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "node_modules/npm/node_modules/make-fetch-happen/node_modules/negotiator": { + "version": "1.0.0", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], + "inBundle": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.6" } }, - "node_modules/cjs-module-lexer": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.1.tgz", - "integrity": "sha512-+CmxIZ/L2vNcEfvNtLdU0ZQ6mbq3FZnwAP2PPTiKP+1QOoKwlKlPgb8UKV0Dds7QVaMnHm+FwSft2VB0s/SLjQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "node_modules/npm/node_modules/minimatch": { + "version": "9.0.5", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "restore-cursor": "^5.0.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=18" + "node": ">=16 || 14 >=14.17" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/cli-truncate": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", - "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", + "node_modules/npm/node_modules/minipass": { + "version": "7.1.2", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-collect": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "slice-ansi": "^7.1.0", - "string-width": "^8.0.0" + "minipass": "^7.0.3" }, "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/cli-truncate/node_modules/string-width": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", - "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "node_modules/npm/node_modules/minipass-fetch": { + "version": "4.0.1", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "get-east-asian-width": "^1.3.0", - "strip-ansi": "^7.1.0" + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" }, "engines": { - "node": ">=20" + "node": "^18.17.0 || >=20.5.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/npm/node_modules/minipass-flush": { + "version": "1.0.5", "dev": true, + "inBundle": true, "license": "ISC", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "minipass": "^3.0.0" }, "engines": { - "node": ">=12" + "node": ">= 8" } }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, "engines": { "node": ">=8" } }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/npm/node_modules/minipass-pipeline": { + "version": "1.2.4", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "minipass": "^3.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "ansi-regex": "^5.0.1" + "yallist": "^4.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "node_modules/npm/node_modules/minipass-sized": { + "version": "1.0.3", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "minipass": "^3.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=8" } }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" + "node": ">=8" } }, - "node_modules/collect-v8-coverage": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", - "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", - "dev": true, - "license": "MIT" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/npm/node_modules/minizlib": { + "version": "3.0.2", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "minipass": "^7.1.2" }, "engines": { - "node": ">=7.0.0" + "node": ">= 18" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "node_modules/npm/node_modules/mkdirp": { + "version": "1.0.4", "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", - "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "inBundle": true, "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, "engines": { - "node": ">=20" + "node": ">=10" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "node_modules/npm/node_modules/ms": { + "version": "2.1.3", "dev": true, + "inBundle": true, "license": "MIT" }, - "node_modules/consola": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "node_modules/npm/node_modules/mute-stream": { + "version": "2.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "engines": { - "node": "^14.18.0 || >=16.10.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "node_modules/npm/node_modules/node-gyp": { + "version": "11.2.0", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "tar": "^7.4.3", + "tinyglobby": "^0.2.12", + "which": "^5.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": ">= 8" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/npm/node_modules/node-gyp/node_modules/chownr": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/mkdirp": { + "version": "3.0.1", + "dev": true, + "inBundle": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.3" + "bin": { + "mkdirp": "dist/cjs/src/bin.js" }, "engines": { - "node": ">=6.0" + "node": ">=10" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/dedent": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", - "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", + "node_modules/npm/node_modules/node-gyp/node_modules/tar": { + "version": "7.4.3", "dev": true, - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } + "engines": { + "node": ">=18" } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "node_modules/npm/node_modules/node-gyp/node_modules/yallist": { + "version": "5.0.0", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "node_modules/npm/node_modules/nopt": { + "version": "8.1.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "^3.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, "engines": { - "node": ">=0.10.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "node_modules/npm/node_modules/normalize-package-data": { + "version": "7.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^8.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, "engines": { - "node": ">=8" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "node_modules/npm/node_modules/npm-audit-report": { + "version": "6.0.0", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/electron-to-chromium": { - "version": "1.5.267", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", - "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "node_modules/npm/node_modules/npm-bundled": { + "version": "4.0.0", "dev": true, - "license": "ISC" + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "node_modules/npm/node_modules/npm-install-checks": { + "version": "7.1.1", "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "node_modules/npm/node_modules/npm-normalize-package-bin": { + "version": "4.0.0", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/environment": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "node_modules/npm/node_modules/npm-package-arg": { + "version": "12.0.2", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^6.0.0" + }, "engines": { - "node": ">=18" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-packlist": { + "version": "9.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^7.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "node_modules/npm/node_modules/npm-pick-manifest": { + "version": "10.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "dependencies": { - "is-arrayish": "^0.2.1" + "npm-install-checks": "^7.1.0", + "npm-normalize-package-bin": "^4.0.0", + "npm-package-arg": "^12.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/esbuild": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", - "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "node_modules/npm/node_modules/npm-profile": { + "version": "11.0.1", "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0" }, "engines": { - "node": ">=18" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch": { + "version": "18.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/redact": "^3.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^14.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^12.0.0", + "proc-log": "^5.0.0" }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.2", - "@esbuild/android-arm": "0.27.2", - "@esbuild/android-arm64": "0.27.2", - "@esbuild/android-x64": "0.27.2", - "@esbuild/darwin-arm64": "0.27.2", - "@esbuild/darwin-x64": "0.27.2", - "@esbuild/freebsd-arm64": "0.27.2", - "@esbuild/freebsd-x64": "0.27.2", - "@esbuild/linux-arm": "0.27.2", - "@esbuild/linux-arm64": "0.27.2", - "@esbuild/linux-ia32": "0.27.2", - "@esbuild/linux-loong64": "0.27.2", - "@esbuild/linux-mips64el": "0.27.2", - "@esbuild/linux-ppc64": "0.27.2", - "@esbuild/linux-riscv64": "0.27.2", - "@esbuild/linux-s390x": "0.27.2", - "@esbuild/linux-x64": "0.27.2", - "@esbuild/netbsd-arm64": "0.27.2", - "@esbuild/netbsd-x64": "0.27.2", - "@esbuild/openbsd-arm64": "0.27.2", - "@esbuild/openbsd-x64": "0.27.2", - "@esbuild/openharmony-arm64": "0.27.2", - "@esbuild/sunos-x64": "0.27.2", - "@esbuild/win32-arm64": "0.27.2", - "@esbuild/win32-ia32": "0.27.2", - "@esbuild/win32-x64": "0.27.2" + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/npm/node_modules/npm-user-validate": { + "version": "3.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "BSD-2-Clause", "engines": { - "node": ">=6" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/npm/node_modules/p-map": { + "version": "7.0.3", "dev": true, + "inBundle": true, "license": "MIT", "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", - "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "node_modules/npm/node_modules/package-json-from-dist": { + "version": "1.0.1", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/npm/node_modules/pacote": { + "version": "19.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.2", - "@eslint/plugin-kit": "^0.4.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" + "@npmcli/git": "^6.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^9.0.0", + "cacache": "^19.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^12.0.0", + "npm-packlist": "^9.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^3.0.0", + "ssri": "^12.0.0", + "tar": "^6.1.11" }, "bin": { - "eslint": "bin/eslint.js" + "pacote": "bin/index.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/eslint-config-prettier": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.2.tgz", - "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", + "node_modules/npm/node_modules/parse-conflict-json": { + "version": "4.0.0", "dev": true, - "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" }, - "peerDependencies": { - "eslint": ">=7.0.0" + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "node_modules/npm/node_modules/path-key": { + "version": "3.1.1", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, + "inBundle": true, + "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=8" } }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "node_modules/npm/node_modules/path-scurry": { + "version": "1.11.1", "dev": true, - "license": "Apache-2.0", + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=16 || 14 >=14.18" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "node_modules/npm/node_modules/postcss-selector-parser": { + "version": "7.1.0", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, - "funding": { - "url": "https://opencollective.com/eslint" + "engines": { + "node": ">=4" } }, - "node_modules/eslint/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "node_modules/npm/node_modules/proc-log": { + "version": "5.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "engines": { - "node": ">= 4" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/npm/node_modules/proggy": { + "version": "3.0.0", "dev": true, + "inBundle": true, "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, "engines": { - "node": "*" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "node_modules/npm/node_modules/promise-all-reject-late": { + "version": "1.0.1", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, + "inBundle": true, + "license": "ISC", "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "node_modules/npm/node_modules/promise-call-limit": { + "version": "3.0.2", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, + "inBundle": true, + "license": "ISC", "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "node_modules/npm/node_modules/promise-retry": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" }, "engines": { - "node": ">=4" + "node": ">=10" } }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "node_modules/npm/node_modules/promzard": { + "version": "2.0.0", "dev": true, - "license": "BSD-3-Clause", + "inBundle": true, + "license": "ISC", "dependencies": { - "estraverse": "^5.1.0" + "read": "^4.0.0" }, "engines": { - "node": ">=0.10" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "node_modules/npm/node_modules/qrcode-terminal": { + "version": "0.12.0", "dev": true, - "license": "BSD-2-Clause", + "inBundle": true, + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/npm/node_modules/read": { + "version": "4.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", "dependencies": { - "estraverse": "^5.2.0" + "mute-stream": "^2.0.0" }, "engines": { - "node": ">=4.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/npm/node_modules/read-cmd-shim": { + "version": "5.0.0", "dev": true, - "license": "BSD-2-Clause", + "inBundle": true, + "license": "ISC", "engines": { - "node": ">=4.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "node_modules/npm/node_modules/read-package-json-fast": { + "version": "4.0.0", "dev": true, - "license": "BSD-2-Clause", + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "node_modules/npm/node_modules/retry": { + "version": "0.12.0", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "node_modules/npm/node_modules/safer-buffer": { + "version": "2.1.2", "dev": true, + "inBundle": true, "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" + "optional": true + }, + "node_modules/npm/node_modules/semver": { + "version": "7.7.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/execa/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "node_modules/npm/node_modules/shebang-command": { + "version": "2.0.0", "dev": true, - "license": "ISC" + "inBundle": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/exit-x": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", - "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", + "node_modules/npm/node_modules/shebang-regex": { + "version": "3.0.0", "dev": true, + "inBundle": true, "license": "MIT", "engines": { - "node": ">= 0.8.0" + "node": ">=8" } }, - "node_modules/expect": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", - "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", + "node_modules/npm/node_modules/signal-exit": { + "version": "4.1.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/sigstore": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", "dependencies": { - "@jest/expect-utils": "30.2.0", - "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-util": "30.2.0" + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0", + "@sigstore/sign": "^3.1.0", + "@sigstore/tuf": "^3.1.0", + "@sigstore/verify": "^2.1.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/bundle": { + "version": "3.1.0", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.4.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/core": { + "version": "2.0.0", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/sign": { + "version": "3.1.0", "dev": true, - "license": "MIT" + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0", + "make-fetch-happen": "^14.0.2", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/verify": { + "version": "2.1.1", "dev": true, + "inBundle": true, "license": "Apache-2.0", "dependencies": { - "bser": "2.1.1" + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "node_modules/npm/node_modules/smart-buffer": { + "version": "4.2.0", "dev": true, + "inBundle": true, "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, "engines": { - "node": ">=16.0.0" + "node": ">= 6.0.0", + "npm": ">= 3.0.0" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "node_modules/npm/node_modules/socks": { + "version": "2.8.5", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" }, "engines": { - "node": ">=8" + "node": ">= 10.0.0", + "npm": ">= 3.0.0" } }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/npm/node_modules/socks-proxy-agent": { + "version": "8.0.5", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 14" } }, - "node_modules/fix-dts-default-cjs-exports": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", - "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", + "node_modules/npm/node_modules/spdx-correct": { + "version": "3.2.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "Apache-2.0", "dependencies": { - "magic-string": "^0.30.17", - "mlly": "^1.7.4", - "rollup": "^4.34.8" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { + "version": "3.0.1", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "node_modules/npm/node_modules/spdx-exceptions": { + "version": "2.5.0", "dev": true, - "license": "ISC" + "inBundle": true, + "license": "CC-BY-3.0" }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "node_modules/npm/node_modules/spdx-expression-parse": { + "version": "4.0.0", "dev": true, - "license": "ISC", + "inBundle": true, + "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "node_modules/npm/node_modules/spdx-license-ids": { + "version": "3.0.21", "dev": true, - "license": "ISC" + "inBundle": true, + "license": "CC0-1.0" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/npm/node_modules/sprintf-js": { + "version": "1.1.3", "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "inBundle": true, + "license": "BSD-3-Clause" + }, + "node_modules/npm/node_modules/ssri": { + "version": "12.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "node_modules/npm/node_modules/string-width": { + "version": "4.2.3", "dev": true, + "inBundle": true, "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, "engines": { - "node": ">=6.9.0" + "node": ">=8" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "node_modules/npm/node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", "dev": true, - "license": "ISC", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">=8" } }, - "node_modules/get-east-asian-width": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", - "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "node_modules/npm/node_modules/strip-ansi": { + "version": "6.0.1", "dev": true, + "inBundle": true, "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "ansi-regex": "^5.0.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=8" } }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "node_modules/npm/node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", "dev": true, + "inBundle": true, "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, "engines": { - "node": ">=8.0.0" + "node": ">=8" } }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "node_modules/npm/node_modules/supports-color": { + "version": "9.4.0", "dev": true, + "inBundle": true, "license": "MIT", "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "node_modules/npm/node_modules/tar": { + "version": "6.2.1", "dev": true, + "inBundle": true, "license": "ISC", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=10" } }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "node_modules/npm/node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", "dev": true, + "inBundle": true, "license": "ISC", "dependencies": { - "is-glob": "^4.0.3" + "minipass": "^3.0.0" }, "engines": { - "node": ">=10.13.0" + "node": ">= 8" } }, - "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=8" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "node_modules/npm/node_modules/tar/node_modules/minipass": { + "version": "5.0.0", "dev": true, - "license": "ISC" + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=8" + } }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "node_modules/npm/node_modules/tar/node_modules/minizlib": { + "version": "2.1.2", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" + "minipass": "^3.0.0", + "yallist": "^4.0.0" }, "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" + "node": ">= 8" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/npm/node_modules/tar/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, "engines": { "node": ">=8" } }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "node_modules/npm/node_modules/text-table": { + "version": "0.2.0", "dev": true, + "inBundle": true, "license": "MIT" }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "node_modules/npm/node_modules/tiny-relative-date": { + "version": "1.3.0", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } + "inBundle": true, + "license": "MIT" }, - "node_modules/husky": { - "version": "9.1.7", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", - "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "node_modules/npm/node_modules/tinyglobby": { + "version": "0.2.14", "dev": true, + "inBundle": true, "license": "MIT", - "bin": { - "husky": "bin.js" + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" }, "engines": { - "node": ">=18" + "node": ">=12.0.0" }, "funding": { - "url": "https://github.com/sponsors/typicode" + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "node_modules/npm/node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.6", "dev": true, + "inBundle": true, "license": "MIT", - "engines": { - "node": ">= 4" + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "node_modules/npm/node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", "dev": true, + "inBundle": true, "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, "engines": { - "node": ">=6" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "node_modules/npm/node_modules/treeverse": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/tuf-js": { + "version": "3.0.1", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" + "@tufjs/models": "3.0.1", + "debug": "^4.3.6", + "make-fetch-happen": "^14.0.1" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "node_modules/npm/node_modules/tuf-js/node_modules/@tufjs/models": { + "version": "3.0.1", "dev": true, + "inBundle": true, "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.5" + }, "engines": { - "node": ">=0.8.19" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "node_modules/npm/node_modules/unique-filename": { + "version": "4.0.0", "dev": true, + "inBundle": true, "license": "ISC", "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "unique-slug": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "node_modules/npm/node_modules/unique-slug": { + "version": "5.0.0", "dev": true, - "license": "ISC" + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", "dev": true, + "inBundle": true, "license": "MIT" }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/npm/node_modules/validate-npm-package-license": { + "version": "3.0.4", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { + "version": "3.0.1", "dev": true, + "inBundle": true, "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-name": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", "engines": { - "node": ">=0.10.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/npm/node_modules/walk-up-path": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/which": { + "version": "5.0.0", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, "engines": { - "node": ">=8" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "node_modules/npm/node_modules/which/node_modules/isexe": { + "version": "3.1.1", "dev": true, - "license": "MIT", + "inBundle": true, + "license": "ISC", "engines": { - "node": ">=6" + "node": ">=16" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/npm/node_modules/wrap-ansi": { + "version": "8.1.0", "dev": true, + "inBundle": true, "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/is-number": { + "node_modules/npm/node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "inBundle": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, "engines": { - "node": ">=0.12.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, + "inBundle": true, "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.1.0", "dev": true, - "license": "BSD-3-Clause", + "inBundle": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } + "inBundle": true, + "license": "MIT" }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", "dev": true, - "license": "BSD-3-Clause", + "inBundle": true, + "license": "MIT", "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=10" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", "dev": true, - "license": "BSD-3-Clause", + "inBundle": true, + "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=10" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "node_modules/npm/node_modules/write-file-atomic": { + "version": "6.0.0", "dev": true, - "license": "BSD-3-Clause", + "inBundle": true, + "license": "ISC", "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" }, "engines": { - "node": ">=8" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "node_modules/npm/node_modules/yallist": { + "version": "4.0.0", "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } + "inBundle": true, + "license": "ISC" }, - "node_modules/jest": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", - "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/core": "30.2.0", - "@jest/types": "30.2.0", - "import-local": "^3.2.0", - "jest-cli": "30.2.0" - }, - "bin": { - "jest": "bin/jest.js" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "node": ">=0.10.0" } }, - "node_modules/jest-changed-files": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz", - "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==", + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "execa": "^5.1.1", - "jest-util": "30.2.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "wrappy": "1" } }, - "node_modules/jest-circus": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", - "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/expect": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "co": "^4.6.0", - "dedent": "^1.6.0", - "is-generator-fn": "^2.1.0", - "jest-each": "30.2.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-runtime": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", - "p-limit": "^3.1.0", - "pretty-format": "30.2.0", - "pure-rand": "^7.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" + "mimic-fn": "^2.1.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-cli": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz", - "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==", + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", - "chalk": "^4.1.2", - "exit-x": "^0.2.2", - "import-local": "^3.2.0", - "jest-config": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "yargs": "^17.7.2" - }, - "bin": { - "jest": "bin/jest.js" + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "node": ">= 0.8.0" } }, - "node_modules/jest-config": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", - "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", + "node_modules/p-each-series": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz", + "integrity": "sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/core": "^7.27.4", - "@jest/get-type": "30.1.0", - "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.2.0", - "@jest/types": "30.2.0", - "babel-jest": "30.2.0", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "deepmerge": "^4.3.1", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "jest-circus": "30.2.0", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-runner": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "micromatch": "^4.0.8", - "parse-json": "^5.2.0", - "pretty-format": "30.2.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "esbuild-register": ">=3.4.0", - "ts-node": ">=9.0.0" + "node": ">=12" }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "esbuild-register": { - "optional": true - }, - "ts-node": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-diff": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", - "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", + "node_modules/p-event": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-6.0.1.tgz", + "integrity": "sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==", "dev": true, "license": "MIT", "dependencies": { - "@jest/diff-sequences": "30.0.1", - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "pretty-format": "30.2.0" + "p-timeout": "^6.1.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-docblock": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", - "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", + "node_modules/p-filter": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-4.1.0.tgz", + "integrity": "sha512-37/tPdZ3oJwHaS3gNJdenCDB3Tz26i9sjhnguBtvN0vYlRIiDNnvTWkuh+0hETV9rLPdJ3rlL3yVOYPIAnM8rw==", "dev": true, "license": "MIT", "dependencies": { - "detect-newline": "^3.1.0" + "p-map": "^7.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-each": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", - "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", + "node_modules/p-is-promise": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", + "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.2.0", - "chalk": "^4.1.2", - "jest-util": "30.2.0", - "pretty-format": "30.2.0" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" } }, - "node_modules/jest-environment-node": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", - "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.2.0", - "@jest/fake-timers": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "jest-mock": "30.2.0", - "jest-util": "30.2.0", - "jest-validate": "30.2.0" + "yocto-queue": "^0.1.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-haste-map": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", - "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "anymatch": "^3.1.3", - "fb-watchman": "^2.0.2", - "graceful-fs": "^4.2.11", - "jest-regex-util": "30.0.1", - "jest-util": "30.2.0", - "jest-worker": "30.2.0", - "micromatch": "^4.0.8", - "walker": "^1.0.8" + "p-limit": "^3.0.2" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=10" }, - "optionalDependencies": { - "fsevents": "^2.3.3" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-leak-detector": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", - "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", + "node_modules/p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "pretty-format": "30.2.0" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-matcher-utils": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", - "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", + "node_modules/p-reduce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-2.1.0.tgz", + "integrity": "sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "chalk": "^4.1.2", - "jest-diff": "30.2.0", - "pretty-format": "30.2.0" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" } }, - "node_modules/jest-message-util": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", - "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", + "node_modules/p-timeout": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", + "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@jest/types": "30.2.0", - "@types/stack-utils": "^2.0.3", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "micromatch": "^4.0.8", - "pretty-format": "30.2.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" + "engines": { + "node": ">=14.16" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=6" } }, - "node_modules/jest-mock": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", - "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "jest-util": "30.2.0" + "callsites": "^3.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=6" } }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6" + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" }, - "peerDependencies": { - "jest-resolve": "*" + "engines": { + "node": ">=8" }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-regex-util": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", - "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", "dev": true, "license": "MIT", "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jest-resolve": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", - "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", + "node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.2.0", - "jest-validate": "30.2.0", - "slash": "^3.0.0", - "unrs-resolver": "^1.7.11" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "parse5": "^6.0.1" } }, - "node_modules/jest-resolve-dependencies": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz", - "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==", + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", - "dependencies": { - "jest-regex-util": "30.0.1", - "jest-snapshot": "30.2.0" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" } }, - "node_modules/jest-runner": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", - "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/console": "30.2.0", - "@jest/environment": "30.2.0", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.2.0", - "jest-haste-map": "30.2.0", - "jest-leak-detector": "30.2.0", - "jest-message-util": "30.2.0", - "jest-resolve": "30.2.0", - "jest-runtime": "30.2.0", - "jest-util": "30.2.0", - "jest-watcher": "30.2.0", - "jest-worker": "30.2.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=0.10.0" } }, - "node_modules/jest-runtime": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", - "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/environment": "30.2.0", - "@jest/fake-timers": "30.2.0", - "@jest/globals": "30.2.0", - "@jest/source-map": "30.0.1", - "@jest/test-result": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "cjs-module-lexer": "^2.1.0", - "collect-v8-coverage": "^1.0.2", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.2.0", - "jest-message-util": "30.2.0", - "jest-mock": "30.2.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.2.0", - "jest-snapshot": "30.2.0", - "jest-util": "30.2.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" } }, - "node_modules/jest-snapshot": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", - "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "dependencies": { - "@babel/core": "^7.27.4", - "@babel/generator": "^7.27.5", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1", - "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.2.0", - "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.2.0", - "@jest/transform": "30.2.0", - "@jest/types": "30.2.0", - "babel-preset-current-node-syntax": "^1.2.0", - "chalk": "^4.1.2", - "expect": "30.2.0", - "graceful-fs": "^4.2.11", - "jest-diff": "30.2.0", - "jest-matcher-utils": "30.2.0", - "jest-message-util": "30.2.0", - "jest-util": "30.2.0", - "pretty-format": "30.2.0", - "semver": "^7.7.2", - "synckit": "^0.11.8" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/jest-util": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", - "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/types": "30.2.0", - "@types/node": "*", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "graceful-fs": "^4.2.11", - "picomatch": "^4.0.2" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=8" } }, - "node_modules/jest-util/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">=8.6" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/jest-validate": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", - "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.2.0", - "camelcase": "^6.3.0", - "chalk": "^4.1.2", - "leven": "^3.1.0", - "pretty-format": "30.2.0" + "bin": { + "pidtree": "bin/pidtree.js" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=0.10" } }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4" } }, - "node_modules/jest-watcher": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", - "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/test-result": "30.2.0", - "@jest/types": "30.2.0", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "jest-util": "30.2.0", - "string-length": "^4.0.2" - }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">= 6" } }, - "node_modules/jest-worker": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", - "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", + "node_modules/pkg-conf": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", + "integrity": "sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*", - "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.2.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.1.1" + "find-up": "^2.0.0", + "load-json-file": "^4.0.0" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node": ">=4" + } + }, + "node_modules/pkg-conf/node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "locate-path": "^2.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "node": ">=4" } }, - "node_modules/joycon": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", - "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "node_modules/pkg-conf/node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", "dev": true, "license": "MIT", + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, "engines": { - "node": ">=10" + "node": ">=4" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "node_modules/pkg-conf/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "license": "MIT", "dependencies": { - "argparse": "^2.0.1" + "p-try": "^1.0.0" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">=4" } }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "node_modules/pkg-conf/node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", "dev": true, "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" + "dependencies": { + "p-limit": "^1.1.0" }, "engines": { - "node": ">=6" + "node": ">=4" } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "node_modules/pkg-conf/node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=4" + } }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "node_modules/pkg-conf/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", "dev": true, "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, "engines": { - "node": ">=6" + "node": ">=4" } }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "license": "MIT", "dependencies": { - "json-buffer": "3.0.1" + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" + "p-locate": "^4.1.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">=8" } }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, "engines": { - "node": ">=14" + "node": ">=6" }, "funding": { - "url": "https://github.com/sponsors/antonk52" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/lint-staged": { - "version": "16.2.7", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.7.tgz", - "integrity": "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==", + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", "dependencies": { - "commander": "^14.0.2", - "listr2": "^9.0.5", - "micromatch": "^4.0.8", - "nano-spawn": "^2.0.0", - "pidtree": "^0.6.0", - "string-argv": "^0.3.2", - "yaml": "^2.8.1" - }, - "bin": { - "lint-staged": "bin/lint-staged.js" + "p-limit": "^2.2.0" }, "engines": { - "node": ">=20.17" - }, - "funding": { - "url": "https://opencollective.com/lint-staged" + "node": ">=8" } }, - "node_modules/listr2": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", - "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", "dev": true, "license": "MIT", "dependencies": { - "cli-truncate": "^5.0.0", - "colorette": "^2.0.20", - "eventemitter3": "^5.0.1", - "log-update": "^6.1.0", - "rfdc": "^1.4.1", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=20.0.0" + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" } }, - "node_modules/listr2/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, "engines": { - "node": ">=12" + "node": ">= 18" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } } }, - "node_modules/listr2/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } }, - "node_modules/listr2/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "node_modules/prettier": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", + "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "dev": true, "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" + "bin": { + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=18" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/listr2/node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "node_modules/pretty-format": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", + "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/load-tsconfig": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", - "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "node_modules/pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", "dev": true, "license": "MIT", "dependencies": { - "p-locate": "^5.0.0" + "parse-ms": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true, "license": "MIT" }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", "dev": true, - "license": "MIT" + "license": "ISC" }, - "node_modules/log-update": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-escapes": "^7.0.0", - "cli-cursor": "^5.0.0", - "slice-ansi": "^7.1.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, - "node_modules/log-update/node_modules/ansi-escapes": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", - "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", + "node_modules/pure-rand": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", "dev": true, - "license": "MIT", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", "dependencies": { - "environment": "^1.0.0" + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "bin": { + "rc": "cli.js" } }, - "node_modules/log-update/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=0.10.0" } }, - "node_modules/log-update/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, "license": "MIT" }, - "node_modules/log-update/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "node_modules/read-package-up": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", + "integrity": "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" + "find-up-simple": "^1.0.0", + "read-pkg": "^9.0.0", + "type-fest": "^4.6.0" }, "engines": { "node": ">=18" @@ -5483,112 +11373,106 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "node_modules/read-package-up/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=18" + "node": ">=16" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "node_modules/read-pkg": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "node_modules/read-pkg-up": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-11.0.0.tgz", + "integrity": "sha512-LOVbvF1Q0SZdjClSefZ0Nz5z8u+tIE7mV5NibzmE9VYmDe9CaBbAVtz1veOSZbofrdsilxuDAYnFenukZVp8/Q==", + "deprecated": "Renamed to read-package-up", "dev": true, "license": "MIT", "dependencies": { - "semver": "^7.5.3" + "find-up-simple": "^1.0.0", + "read-pkg": "^9.0.0", + "type-fest": "^4.6.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tmpl": "1.0.5" + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "node_modules/read-pkg/node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", "dev": true, "license": "MIT", "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" }, "engines": { - "node": ">=8.6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "node_modules/read-pkg/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "dev": true, - "license": "MIT", + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=6" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "node_modules/read-pkg/node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", "dev": true, "license": "MIT", "engines": { @@ -5598,641 +11482,802 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "license": "ISC", + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" - }, + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">= 14.18.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "node_modules/registry-auth-token": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.0.tgz", + "integrity": "sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==", "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "@pnpm/npm-conf": "^2.1.0" + }, + "engines": { + "node": ">=14" } }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=0.10.0" } }, - "node_modules/mlly": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", - "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "dev": true, "license": "MIT", "dependencies": { - "acorn": "^8.15.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.1" + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" + "engines": { + "node": ">=4" } }, - "node_modules/nano-spawn": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz", - "integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==", + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dev": true, "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, "engines": { - "node": ">=20.17" + "node": ">=18" }, "funding": { - "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/napi-postinstall": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", - "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "node_modules/restore-cursor/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "dev": true, "license": "MIT", - "bin": { - "napi-postinstall": "lib/cli.js" + "dependencies": { + "mimic-function": "^5.0.0" }, "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + "node": ">=18" }, "funding": { - "url": "https://opencollective.com/napi-postinstall" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "dev": true, "license": "MIT" }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node_modules/rollup": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", + "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", "dev": true, "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, "engines": { - "node": ">=0.10.0" + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.54.0", + "@rollup/rollup-android-arm64": "4.54.0", + "@rollup/rollup-darwin-arm64": "4.54.0", + "@rollup/rollup-darwin-x64": "4.54.0", + "@rollup/rollup-freebsd-arm64": "4.54.0", + "@rollup/rollup-freebsd-x64": "4.54.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", + "@rollup/rollup-linux-arm-musleabihf": "4.54.0", + "@rollup/rollup-linux-arm64-gnu": "4.54.0", + "@rollup/rollup-linux-arm64-musl": "4.54.0", + "@rollup/rollup-linux-loong64-gnu": "4.54.0", + "@rollup/rollup-linux-ppc64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-musl": "4.54.0", + "@rollup/rollup-linux-s390x-gnu": "4.54.0", + "@rollup/rollup-linux-x64-gnu": "4.54.0", + "@rollup/rollup-linux-x64-musl": "4.54.0", + "@rollup/rollup-openharmony-arm64": "4.54.0", + "@rollup/rollup-win32-arm64-msvc": "4.54.0", + "@rollup/rollup-win32-ia32-msvc": "4.54.0", + "@rollup/rollup-win32-x64-gnu": "4.54.0", + "@rollup/rollup-win32-x64-msvc": "4.54.0", + "fsevents": "~2.3.2" } }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT", "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" + "queue-microtask": "^1.2.2" } }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, - "license": "MIT", + "license": "MIT" + }, + "node_modules/semantic-release": { + "version": "23.1.1", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-23.1.1.tgz", + "integrity": "sha512-qqJDBhbtHsjUEMsojWKGuL5lQFCJuPtiXKEIlFKyTzDDGTAE/oyvznaP8GeOr5PvcqBJ6LQz4JCENWPLeehSpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@semantic-release/commit-analyzer": "^12.0.0", + "@semantic-release/error": "^4.0.0", + "@semantic-release/github": "^10.0.0", + "@semantic-release/npm": "^12.0.0", + "@semantic-release/release-notes-generator": "^13.0.0", + "aggregate-error": "^5.0.0", + "cosmiconfig": "^9.0.0", + "debug": "^4.0.0", + "env-ci": "^11.0.0", + "execa": "^9.0.0", + "figures": "^6.0.0", + "find-versions": "^6.0.0", + "get-stream": "^6.0.0", + "git-log-parser": "^1.2.0", + "hook-std": "^3.0.0", + "hosted-git-info": "^7.0.0", + "import-from-esm": "^1.3.1", + "lodash-es": "^4.17.21", + "marked": "^12.0.0", + "marked-terminal": "^7.0.0", + "micromatch": "^4.0.2", + "p-each-series": "^3.0.0", + "p-reduce": "^3.0.0", + "read-package-up": "^11.0.0", + "resolve-from": "^5.0.0", + "semver": "^7.3.2", + "semver-diff": "^4.0.0", + "signale": "^1.2.1", + "yargs": "^17.5.1" + }, + "bin": { + "semantic-release": "bin/semantic-release.js" + }, "engines": { - "node": ">=0.10.0" + "node": ">=20.8.1" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "node_modules/semantic-release/node_modules/@octokit/auth-token": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz", + "integrity": "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==", "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" + "license": "MIT", + "engines": { + "node": ">= 18" } }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "node_modules/semantic-release/node_modules/@octokit/core": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.6.tgz", + "integrity": "sha512-kIU8SLQkYWGp3pVKiYzA5OSaNF5EE03P/R8zEmmrG6XwOg5oBjXyQVVIauQ0dgau4zYhpZEhJrvIYt6oM+zZZA==", "dev": true, "license": "MIT", "dependencies": { - "mimic-fn": "^2.1.0" + "@octokit/auth-token": "^5.0.0", + "@octokit/graphql": "^8.2.2", + "@octokit/request": "^9.2.3", + "@octokit/request-error": "^6.1.8", + "@octokit/types": "^14.0.0", + "before-after-hook": "^3.0.2", + "universal-user-agent": "^7.0.0" }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 18" } }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "node_modules/semantic-release/node_modules/@octokit/core/node_modules/@octokit/types": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", + "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^25.1.0" + } + }, + "node_modules/semantic-release/node_modules/@octokit/endpoint": { + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz", + "integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==", "dev": true, "license": "MIT", "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.2" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" } }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "node_modules/semantic-release/node_modules/@octokit/endpoint/node_modules/@octokit/types": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", + "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", "dev": true, "license": "MIT", "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@octokit/openapi-types": "^25.1.0" } }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/semantic-release/node_modules/@octokit/graphql": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.2.2.tgz", + "integrity": "sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA==", "dev": true, "license": "MIT", "dependencies": { - "p-limit": "^3.0.2" + "@octokit/request": "^9.2.3", + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 18" } }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "node_modules/semantic-release/node_modules/@octokit/graphql/node_modules/@octokit/types": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", + "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6" + "dependencies": { + "@octokit/openapi-types": "^25.1.0" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "node_modules/semantic-release/node_modules/@octokit/openapi-types": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", + "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", "dev": true, - "license": "BlueOak-1.0.0" + "license": "MIT" }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "node_modules/semantic-release/node_modules/@octokit/plugin-paginate-rest": { + "version": "11.6.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.6.0.tgz", + "integrity": "sha512-n5KPteiF7pWKgBIBJSk8qzoZWcUkza2O6A0za97pMGVrGfPdltxrfmfF5GucHYvHGZD8BdaZmmHGz5cX/3gdpw==", "dev": true, "license": "MIT", "dependencies": { - "callsites": "^3.0.0" + "@octokit/types": "^13.10.0" }, "engines": { - "node": ">=6" + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" } }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "node_modules/semantic-release/node_modules/@octokit/plugin-retry": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-7.2.1.tgz", + "integrity": "sha512-wUc3gv0D6vNHpGxSaR3FlqJpTXGWgqmk607N9L3LvPL4QjaxDgX/1nY2mGpT37Khn+nlIXdljczkRnNdTTV3/A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" + "@octokit/request-error": "^6.1.8", + "@octokit/types": "^14.0.0", + "bottleneck": "^2.15.3" }, "engines": { - "node": ">=8" + "node": ">= 18" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@octokit/core": ">=6" } }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "node_modules/semantic-release/node_modules/@octokit/plugin-retry/node_modules/@octokit/types": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", + "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", "dev": true, "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "@octokit/openapi-types": "^25.1.0" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "node_modules/semantic-release/node_modules/@octokit/plugin-throttling": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-9.6.1.tgz", + "integrity": "sha512-bt3EBUkeKUzDQXRCcFrR9SWVqlLFRRqcCrr6uAorWt6NXTyjMKqcGrFmXqJy9NCbnKgiIZ2OXWq04theFc76Jg==", "dev": true, "license": "MIT", + "dependencies": { + "@octokit/types": "^13.7.0", + "bottleneck": "^2.15.3" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "^6.1.3" } }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "node_modules/semantic-release/node_modules/@octokit/request": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.4.tgz", + "integrity": "sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA==", "dev": true, "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^10.1.4", + "@octokit/request-error": "^6.1.8", + "@octokit/types": "^14.0.0", + "fast-content-type-parse": "^2.0.0", + "universal-user-agent": "^7.0.2" + }, "engines": { - "node": ">=8" + "node": ">= 18" } }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "node_modules/semantic-release/node_modules/@octokit/request-error": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.8.tgz", + "integrity": "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "@octokit/types": "^14.0.0" }, "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">= 18" } }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "node_modules/semantic-release/node_modules/@octokit/request-error/node_modules/@octokit/types": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", + "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", "dev": true, - "license": "ISC" + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^25.1.0" + } }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "node_modules/semantic-release/node_modules/@octokit/request/node_modules/@octokit/types": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", + "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", "dev": true, "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "dependencies": { + "@octokit/openapi-types": "^25.1.0" } }, - "node_modules/pidtree": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", - "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "node_modules/semantic-release/node_modules/@semantic-release/commit-analyzer": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-12.0.0.tgz", + "integrity": "sha512-qG+md5gdes+xa8zP7lIo1fWE17zRdO8yMCaxh9lyL65TQleoSv8WHHOqRURfghTytUh+NpkSyBprQ5hrkxOKVQ==", "dev": true, "license": "MIT", - "bin": { - "pidtree": "bin/pidtree.js" + "dependencies": { + "conventional-changelog-angular": "^7.0.0", + "conventional-commits-filter": "^4.0.0", + "conventional-commits-parser": "^5.0.0", + "debug": "^4.0.0", + "import-from-esm": "^1.0.3", + "lodash-es": "^4.17.21", + "micromatch": "^4.0.2" }, "engines": { - "node": ">=0.10" + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" } }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "node_modules/semantic-release/node_modules/@semantic-release/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 6" + "node": ">=18" } }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "node_modules/semantic-release/node_modules/@semantic-release/github": { + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-10.3.5.tgz", + "integrity": "sha512-svvRglGmvqvxjmDgkXhrjf0lC88oZowFhOfifTldbgX9Dzj0inEtMLaC+3/MkDEmxmaQjWmF5Q/0CMIvPNSVdQ==", "dev": true, "license": "MIT", "dependencies": { - "find-up": "^4.0.0" + "@octokit/core": "^6.0.0", + "@octokit/plugin-paginate-rest": "^11.0.0", + "@octokit/plugin-retry": "^7.0.0", + "@octokit/plugin-throttling": "^9.0.0", + "@semantic-release/error": "^4.0.0", + "aggregate-error": "^5.0.0", + "debug": "^4.3.4", + "dir-glob": "^3.0.1", + "globby": "^14.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "issue-parser": "^7.0.0", + "lodash-es": "^4.17.21", + "mime": "^4.0.0", + "p-filter": "^4.0.0", + "url-join": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" } }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/semantic-release/node_modules/@semantic-release/npm": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-12.0.2.tgz", + "integrity": "sha512-+M9/Lb35IgnlUO6OSJ40Ie+hUsZLuph2fqXC/qrKn0fMvUU/jiCjpoL6zEm69vzcmaZJ8yNKtMBEKHWN49WBbQ==", "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "@semantic-release/error": "^4.0.0", + "aggregate-error": "^5.0.0", + "execa": "^9.0.0", + "fs-extra": "^11.0.0", + "lodash-es": "^4.17.21", + "nerf-dart": "^1.0.0", + "normalize-url": "^8.0.0", + "npm": "^10.9.3", + "rc": "^1.2.8", + "read-pkg": "^9.0.0", + "registry-auth-token": "^5.0.0", + "semver": "^7.1.2", + "tempy": "^3.0.0" }, "engines": { - "node": ">=8" + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" } }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "node_modules/semantic-release/node_modules/@semantic-release/release-notes-generator": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-13.0.0.tgz", + "integrity": "sha512-LEeZWb340keMYuREMyxrODPXJJ0JOL8D/mCl74B4LdzbxhtXV2LrPN2QBEcGJrlQhoqLO0RhxQb6masHytKw+A==", "dev": true, "license": "MIT", "dependencies": { - "p-locate": "^4.1.0" + "conventional-changelog-angular": "^7.0.0", + "conventional-changelog-writer": "^7.0.0", + "conventional-commits-filter": "^4.0.0", + "conventional-commits-parser": "^5.0.0", + "debug": "^4.0.0", + "get-stream": "^7.0.0", + "import-from-esm": "^1.0.3", + "into-stream": "^7.0.0", + "lodash-es": "^4.17.21", + "read-pkg-up": "^11.0.0" }, "engines": { - "node": ">=8" + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" } }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/semantic-release/node_modules/@semantic-release/release-notes-generator/node_modules/get-stream": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-7.0.1.tgz", + "integrity": "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==", "dev": true, "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, "engines": { - "node": ">=6" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/semantic-release/node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", "dev": true, "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "node_modules/semantic-release/node_modules/aggregate-error": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", + "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", "dev": true, "license": "MIT", "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" + "clean-stack": "^5.2.0", + "indent-string": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/postcss-load-config": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", - "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "node_modules/semantic-release/node_modules/before-after-hook": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", + "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/semantic-release/node_modules/clean-stack": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.3.0.tgz", + "integrity": "sha512-9ngPTOhYGQqNVSfeJkYXHmF7AGWp4/nN5D/QqNQs3Dvxd1Kk/WpjHfNujKHYUQ/5CoGyOyFNoWSPk5afzP0QVg==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", "dependencies": { - "lilconfig": "^3.1.1" + "escape-string-regexp": "5.0.0" }, "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "jiti": ">=1.21.0", - "postcss": ">=8.0.9", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - }, - "postcss": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "node_modules/semantic-release/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/prettier": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", - "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", + "node_modules/semantic-release/node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", "dev": true, "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" }, "engines": { - "node": ">=14" + "node": "^18.19.0 || >=20.5.0" }, "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/pretty-format": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", - "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", + "node_modules/semantic-release/node_modules/execa/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/semantic-release/node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/semantic-release/node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "node_modules/semantic-release/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pure-rand": { + "node_modules/semantic-release/node_modules/issue-parser": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", - "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-7.0.1.tgz", + "integrity": "sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "lodash.capitalize": "^4.2.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.uniqby": "^4.7.0" + }, + "engines": { + "node": "^18.17 || >=20.6.1" + } }, - "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "node_modules/semantic-release/node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", "dev": true, "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, "engines": { - "node": ">= 14.18.0" + "node": ">=18" }, "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "node_modules/semantic-release/node_modules/p-reduce": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-3.0.0.tgz", + "integrity": "sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "node_modules/semantic-release/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/resolve-cwd/node_modules/resolve-from": { + "node_modules/semantic-release/node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", @@ -6242,144 +12287,220 @@ "node": ">=8" } }, - "node_modules/resolve-from": { + "node_modules/semantic-release/node_modules/strip-final-newline": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "node_modules/semantic-release/node_modules/universal-user-agent": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", + "dev": true, + "license": "ISC" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", + "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", "dev": true, "license": "MIT", "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" + "semver": "^7.3.5" }, "engines": { - "node": ">=18" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/restore-cursor/node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "node_modules/semver-regex": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz", + "integrity": "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==", "dev": true, "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, "engines": { - "node": ">=18" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/rollup": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", - "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/signale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/signale/-/signale-1.4.0.tgz", + "integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.8" + "chalk": "^2.3.2", + "figures": "^2.0.0", + "pkg-conf": "^2.1.0" }, - "bin": { - "rollup": "dist/bin/rollup" + "engines": { + "node": ">=6" + } + }, + "node_modules/signale/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "node": ">=4" + } + }, + "node_modules/signale/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.54.0", - "@rollup/rollup-android-arm64": "4.54.0", - "@rollup/rollup-darwin-arm64": "4.54.0", - "@rollup/rollup-darwin-x64": "4.54.0", - "@rollup/rollup-freebsd-arm64": "4.54.0", - "@rollup/rollup-freebsd-x64": "4.54.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", - "@rollup/rollup-linux-arm-musleabihf": "4.54.0", - "@rollup/rollup-linux-arm64-gnu": "4.54.0", - "@rollup/rollup-linux-arm64-musl": "4.54.0", - "@rollup/rollup-linux-loong64-gnu": "4.54.0", - "@rollup/rollup-linux-ppc64-gnu": "4.54.0", - "@rollup/rollup-linux-riscv64-gnu": "4.54.0", - "@rollup/rollup-linux-riscv64-musl": "4.54.0", - "@rollup/rollup-linux-s390x-gnu": "4.54.0", - "@rollup/rollup-linux-x64-gnu": "4.54.0", - "@rollup/rollup-linux-x64-musl": "4.54.0", - "@rollup/rollup-openharmony-arm64": "4.54.0", - "@rollup/rollup-win32-arm64-msvc": "4.54.0", - "@rollup/rollup-win32-ia32-msvc": "4.54.0", - "@rollup/rollup-win32-x64-gnu": "4.54.0", - "@rollup/rollup-win32-x64-msvc": "4.54.0", - "fsevents": "~2.3.2" + "engines": { + "node": ">=4" + } + }, + "node_modules/signale/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" } }, - "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, + "node_modules/signale/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/signale/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=0.8.0" } }, - "node_modules/shebang-command": { + "node_modules/signale/node_modules/figures": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", "dev": true, "license": "MIT", "dependencies": { - "shebang-regex": "^3.0.0" + "escape-string-regexp": "^1.0.5" }, "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/shebang-regex": { + "node_modules/signale/node_modules/has-flag": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "node_modules/signale/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, "engines": { - "node": ">=14" + "node": ">=4" + } + }, + "node_modules/skin-tone": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", + "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-emoji-modifier-base": "^1.0.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=8" } }, "node_modules/slash": { @@ -6459,6 +12580,59 @@ "source-map": "^0.6.0" } }, + "node_modules/spawn-error-forwarder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz", + "integrity": "sha512-gRjMgK5uFjbCvdibeGJuy3I5OYz6VLoVdsOJdA6wV0WlfQVLFueoqMxwwYD9RODdgb6oUIvlRlsyFSiQkMKu0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -6489,6 +12663,27 @@ "node": ">=8" } }, + "node_modules/stream-combiner2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", @@ -6706,6 +12901,24 @@ "node": ">= 6" } }, + "node_modules/super-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.1.0.tgz", + "integrity": "sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-timeout": "^1.0.1", + "make-asynchronous": "^1.0.1", + "time-span": "^5.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -6719,6 +12932,23 @@ "node": ">=8" } }, + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, "node_modules/synckit": { "version": "0.11.11", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", @@ -6735,6 +12965,61 @@ "url": "https://opencollective.com/synckit" } }, + "node_modules/temp-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", + "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, + "node_modules/tempy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.1.0.tgz", + "integrity": "sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-stream": "^3.0.0", + "temp-dir": "^3.0.0", + "type-fest": "^2.12.2", + "unique-string": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -6796,6 +13081,19 @@ "node": "*" } }, + "node_modules/text-extensions": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz", + "integrity": "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -6819,6 +13117,40 @@ "node": ">=0.8" } }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/time-span": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", + "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "convert-hrtime": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tinyexec": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", @@ -6891,6 +13223,19 @@ "node": ">=8.0" } }, + "node_modules/traverse": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.8.tgz", + "integrity": "sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -7168,6 +13513,62 @@ "dev": true, "license": "MIT" }, + "node_modules/unicode-emoji-modifier-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unique-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", + "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "crypto-random-string": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/unrs-resolver": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", @@ -7244,6 +13645,23 @@ "punycode": "^2.1.0" } }, + "node_modules/url-join": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz", + "integrity": "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", @@ -7259,6 +13677,17 @@ "node": ">=10.12.0" } }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -7269,6 +13698,13 @@ "makeerror": "1.0.12" } }, + "node_modules/web-worker": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz", + "integrity": "sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -7418,6 +13854,16 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -7537,6 +13983,19 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index 16162a8..6700a17 100644 --- a/package.json +++ b/package.json @@ -50,11 +50,20 @@ "engines": { "node": ">=16.0.0" }, + "publishConfig": { + "access": "public" + }, "dependencies": { "@typescript-eslint/typescript-estree": "^8.50.1", "commander": "^14.0.2" }, "devDependencies": { + "@semantic-release/changelog": "^6.0.3", + "@semantic-release/commit-analyzer": "^11.1.0", + "@semantic-release/git": "^10.0.1", + "@semantic-release/github": "^9.2.6", + "@semantic-release/npm": "^11.0.2", + "@semantic-release/release-notes-generator": "^12.1.0", "@types/jest": "^30.0.0", "@types/node": "^24.10.1", "@typescript-eslint/eslint-plugin": "^8.18.1", @@ -65,6 +74,7 @@ "jest": "^30.2.0", "lint-staged": "^16.2.7", "prettier": "^3.4.2", + "semantic-release": "^23.0.0", "ts-jest": "^29.4.5", "tsup": "^8.0.0", "typescript": "^5.9.3", From f4cd0b89f12056a3139dfbe50bc6ff1ec9825e05 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Tue, 30 Dec 2025 22:53:44 +0900 Subject: [PATCH 26/57] feat: translate comments and logs to english in src/cli/fix.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/cli/fix.ts | 123 ++++++++++++++++++++++++------------------------- 1 file changed, 60 insertions(+), 63 deletions(-) diff --git a/src/cli/fix.ts b/src/cli/fix.ts index 9c0d395..91b6835 100644 --- a/src/cli/fix.ts +++ b/src/cli/fix.ts @@ -1,12 +1,12 @@ #!/usr/bin/env tsx /** - * validate:docs:codeで検出されたエラーを対話的に修正するスクリプト + * Interactive script to fix errors detected by validate:docs:code * - * 使用方法: - * tsx scripts/coderef/fix.ts # デフォルト: バックアップなし + * Usage: + * tsx scripts/coderef/fix.ts # Default: no backup * tsx scripts/coderef/fix.ts --dry-run - * tsx scripts/coderef/fix.ts --auto --backup # バックアップを作成する場合 + * tsx scripts/coderef/fix.ts --auto --backup # With backup * npm run coderef:fix */ @@ -20,20 +20,20 @@ import type { CodeRefError, FixOptions, FixResult } from '@/utils/types'; import { extractCodeRefs, findMarkdownFiles, validateCodeRef } from '@/core/validate'; import { loadFixConfig, getDocsPath, type CodeRefFixConfig } from '@/config'; -// コマンドライン引数のパース +// Parse command line arguments function parseArgs(): FixOptions { const args = process.argv.slice(2); return { dryRun: args.includes('--dry-run'), auto: args.includes('--auto'), - noBackup: !args.includes('--backup'), // デフォルトでバックアップなし(--backupで有効化) + noBackup: !args.includes('--backup'), // Default: no backup (enabled with --backup) verbose: args.includes('--verbose') || args.includes('-v'), }; } /** - * グループ化されたエラー + * Grouped errors */ interface ErrorGroup { docFile: string; @@ -41,7 +41,7 @@ interface ErrorGroup { } /** - * エラーを収集 + * Collect errors */ function collectErrors(config: CodeRefFixConfig): ErrorGroup[] { const docsPath = getDocsPath(config); @@ -72,12 +72,12 @@ function collectErrors(config: CodeRefFixConfig): ErrorGroup[] { } /** - * メイン処理 + * Main process */ export async function main(): Promise { const options = parseArgs(); - // 設定を読み込み + // Load configuration const config = loadFixConfig({ dryRun: options.dryRun, auto: options.auto, @@ -85,25 +85,25 @@ export async function main(): Promise { verbose: options.verbose, }); - console.log('🔧 CODE_REFエラーの修正を開始します...\n'); + console.log('🔧 Starting CODE_REF error fixes...\n'); if (options.dryRun) { - console.log('⚠️ DRY RUNモード: 実際の変更は行いません\n'); + console.log('⚠️ DRY RUN mode: No actual changes will be made\n'); } - // エラーを収集 + // Collect errors const errorGroups = collectErrors(config); if (errorGroups.length === 0) { - console.log('✅ 修正可能なエラーは見つかりませんでした'); + console.log('✅ No fixable errors found'); process.exit(0); } - // 統計情報 + // Statistics const totalErrors = errorGroups.reduce((sum, g) => sum + g.errors.length, 0); - console.log(`📊 ${errorGroups.length}個のファイルで${totalErrors}個の修正可能なエラーを検出\n`); + console.log(`📊 Detected ${totalErrors} fixable error(s) in ${errorGroups.length} file(s)\n`); - // 対話インターフェース + // Interactive interface const rl = createPromptInterface(); const fixResults: FixResult[] = []; const backupFiles = new Set(); @@ -111,36 +111,36 @@ export async function main(): Promise { try { for (const group of errorGroups) { console.log(`\n📄 ${path.relative(config.projectRoot, group.docFile)}`); - console.log(` ${group.errors.length}個のエラー\n`); + console.log(` ${group.errors.length} error(s)\n`); - // エラーをdocLineNumber降順(下から上へ)にソート - // 下部の修正が上部の行番号に影響を与えないようにするため + // Sort errors in descending order by docLineNumber (bottom to top) + // To prevent fixes at the bottom from affecting line numbers at the top const sortedErrors = group.errors.sort((a, b) => { const lineA = a.ref.docLineNumber ?? Infinity; const lineB = b.ref.docLineNumber ?? Infinity; - return lineB - lineA; // 降順 + return lineB - lineA; // descending }); - let _lineOffset = 0; // 累積オフセットを追跡(将来のエッジケース対応用) + let _lineOffset = 0; // Track cumulative offset (for future edge case handling) for (const error of sortedErrors) { console.log(`\n❌ ${error.type}: ${error.message}`); console.log( - ` 参照: ${path.relative(config.projectRoot, error.ref.docFile)}${error.ref.docLineNumber ? `:${error.ref.docLineNumber}` : ''}` + ` Reference: ${path.relative(config.projectRoot, error.ref.docFile)}${error.ref.docLineNumber ? `:${error.ref.docLineNumber}` : ''}` ); - // 修正アクションを作成 + // Create fix action let action; if (error.type === 'CODE_LOCATION_MISMATCH') { - // 複数マッチの処理 + // Handle multiple matches action = await handleMultipleMatches(error, rl); } else { const fixActionResult = await createFixAction(error, rl); - // 複数のオプションがある場合、ユーザーに選択させる + // If there are multiple options, let the user choose if (Array.isArray(fixActionResult)) { - console.log('\n🛠️ 修正方法を選択してください:\n'); + console.log('\n🛠️ Please select a fix method:\n'); fixActionResult.forEach((opt, index) => { console.log(` ${index + 1}. ${opt.description}`); @@ -152,28 +152,25 @@ export async function main(): Promise { }); if (options.auto) { - // autoモードの場合は最初のオプションを自動選択 - console.log(' ℹ️ autoモードのため、オプション1を自動選択します\n'); + // Auto-select first option in auto mode + console.log(' ℹ️ Auto-selecting option 1 in auto mode\n'); action = fixActionResult[0]; } else { - // ユーザーに選択させる + // Let user choose const selection = await new Promise((resolve) => { - rl.question( - `修正方法を選択してください (1-${fixActionResult.length}): `, - (answer) => { - const num = parseInt(answer, 10); - if (num >= 1 && num <= fixActionResult.length) { - resolve(num - 1); - } else { - console.log(' ⚠️ 無効な選択です。スキップします。'); - resolve(-1); - } + rl.question(`Select fix method (1-${fixActionResult.length}): `, (answer) => { + const num = parseInt(answer, 10); + if (num >= 1 && num <= fixActionResult.length) { + resolve(num - 1); + } else { + console.log(' ⚠️ Invalid selection. Skipping.'); + resolve(-1); } - ); + }); }); if (selection === -1) { - console.log(' ⏭️ スキップしました'); + console.log(' ⏭️ Skipped'); continue; } @@ -185,56 +182,56 @@ export async function main(): Promise { } if (!action) { - console.log(' ⚠️ このエラーは修正できません'); + console.log(' ⚠️ This error cannot be fixed'); continue; } - // プレビュー表示(単一オプションの場合のみ) + // Display preview (only for single option) if (!Array.isArray(action)) { displayFixPreview(action); } - // 確認 + // Confirmation let shouldFix = options.auto; if (!options.auto) { - shouldFix = await askYesNo(rl, '\nこの修正を適用しますか?', false); + shouldFix = await askYesNo(rl, '\nApply this fix?', false); } if (!shouldFix) { - console.log(' ⏭️ スキップしました'); + console.log(' ⏭️ Skipped'); continue; } - // Dry runチェック + // Dry run check if (options.dryRun) { - console.log(' ✅ [DRY RUN] 修正をシミュレートしました'); + console.log(' ✅ [DRY RUN] Simulated fix'); fixResults.push({ success: true, action }); continue; } - // バックアップ作成(ファイルごとに1回のみ) + // Create backup (once per file) let backupPath: string | undefined; if (!options.noBackup && !backupFiles.has(group.docFile)) { backupPath = createBackup(group.docFile); backupFiles.add(group.docFile); - console.log(` 💾 バックアップ作成: ${path.basename(backupPath)}`); + console.log(` 💾 Backup created: ${path.basename(backupPath)}`); } - // 修正を適用 + // Apply fix try { const lineDelta = applyFix(action); - _lineOffset += lineDelta; // オフセットを累積 + _lineOffset += lineDelta; // Accumulate offset - // デバッグ用のログ + // Debug log if (lineDelta !== 0) { console.log(` 📊 Line delta: ${lineDelta > 0 ? '+' : ''}${lineDelta}`); } - console.log(' ✅ 修正を適用しました'); + console.log(' ✅ Fix applied'); fixResults.push({ success: true, action, backupPath }); } catch (err) { const errorMsg = err instanceof Error ? err.message : String(err); - console.log(` ❌ 修正に失敗しました: ${errorMsg}`); + console.log(` ❌ Fix failed: ${errorMsg}`); fixResults.push({ success: false, action, error: errorMsg, backupPath }); } } @@ -243,18 +240,18 @@ export async function main(): Promise { rl.close(); } - // 結果サマリー + // Result summary console.log(`\n${'='.repeat(60)}`); - console.log('📊 修正結果サマリー\n'); + console.log('📊 Fix Results Summary\n'); const successful = fixResults.filter((r) => r.success).length; const failed = fixResults.filter((r) => !r.success).length; - console.log(`✅ 成功: ${successful}個`); - console.log(`❌ 失敗: ${failed}個`); + console.log(`✅ Successful: ${successful}`); + console.log(`❌ Failed: ${failed}`); if (backupFiles.size > 0 && !options.noBackup) { - console.log(`\n💾 バックアップファイル: ${backupFiles.size}個`); + console.log(`\n💾 Backup files: ${backupFiles.size}`); for (const file of backupFiles) { const backupPath = `${file}.backup`; console.log(` ${path.relative(config.projectRoot, backupPath)}`); @@ -264,10 +261,10 @@ export async function main(): Promise { process.exit(failed > 0 ? 1 : 0); } -// スクリプトとして実行された場合 +// When executed as a script if (require.main === module) { main().catch((error) => { - console.error('\n❌ エラーが発生しました:', error); + console.error('\n❌ An error occurred:', error); process.exit(1); }); } From d7a70531c042c81b9e5aa5095fa8e1000d5cbd1a Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Tue, 30 Dec 2025 22:55:13 +0900 Subject: [PATCH 27/57] ci: translate prompt to english in claude-code-review workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- .github/workflows/claude-code-review.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 96d2cd5..18c524a 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -41,16 +41,16 @@ jobs: REPO: ${{ github.repository }} PR NUMBER: ${{ github.event.pull_request.number }} - このプルリクエストをレビューし、以下の観点についてフィードバックを提供してください。 - - コード品質およびベストプラクティス - - 潜在的なバグや問題点 - - パフォーマンスに関する懸念 - - セキュリティ上の懸念 - - テストカバレッジ - - ドキュメント更新の必要性 - - スタイルやコーディング規約については、リポジトリ内の `CLAUDE.md` をガイドとして参照してください。フィードバックは建設的かつ有益なものにしてください。 - レビュー内容は、Bashツールを使って `gh pr comment` コマンドでPRにコメントとして投稿してください。 + Please review this pull request and provide feedback on the following aspects: + - Code quality and best practices + - Potential bugs or issues + - Performance concerns + - Security concerns + - Test coverage + - Documentation update needs + + For style and coding conventions, please refer to `CLAUDE.md` in the repository as a guide. Feedback should be constructive and helpful. + Please post your review as a comment on the PR using the `gh pr comment` command via the Bash tool. # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md # or https://code.claude.com/docs/en/cli-reference for available options From 7b2dafb44a5f0cd3c0ae3f94640de7447d432339 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Tue, 30 Dec 2025 23:11:37 +0900 Subject: [PATCH 28/57] feat: translate comments and messages to english in src/utils/fix.ts and tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/utils/fix.test.ts | 22 ++--- src/utils/fix.ts | 200 +++++++++++++++++++++--------------------- 2 files changed, 111 insertions(+), 111 deletions(-) diff --git a/src/utils/fix.test.ts b/src/utils/fix.test.ts index 7b2185f..1f281c8 100644 --- a/src/utils/fix.test.ts +++ b/src/utils/fix.test.ts @@ -106,7 +106,7 @@ describe('createLocationMismatchFix', () => { expect(result).toEqual({ type: 'UPDATE_LINE_NUMBERS', error, - description: '行番号を 10-20 から 15-25 に更新', + description: 'Update line numbers from 10-20 to 15-25', preview: expect.stringContaining('test.ts:10-20'), newStartLine: 15, newEndLine: 25, @@ -129,7 +129,7 @@ describe('createLocationMismatchFix', () => { }; expect(() => createLocationMismatchFix(error)).toThrow( - 'CODE_LOCATION_MISMATCHにはsuggestedLinesが必要です' + 'CODE_LOCATION_MISMATCH requires suggestedLines' ); }); }); @@ -164,7 +164,7 @@ describe('createBlockMissingFix', () => { expect(result).toEqual({ type: 'INSERT_CODE_BLOCK', error, - description: 'test.ts:10-20 からコードブロックを挿入', + description: 'Insert code block from test.ts:10-20', preview: expect.stringContaining('code content'), newCodeBlock: 'code content', }); @@ -185,7 +185,7 @@ describe('createBlockMissingFix', () => { }; expect(() => createBlockMissingFix(error)).toThrow( - 'ファイル全体参照にはコードブロックは不要です' + 'Whole file reference does not need code block' ); }); }); @@ -283,7 +283,7 @@ describe('createLineOutOfRangeFix', () => { expect(result).toEqual({ type: 'UPDATE_END_LINE', error, - description: '終了行を 150 から 100 (ファイル末尾) に修正', + description: 'Fix end line from 150 to 100 (end of file)', preview: expect.stringContaining('10-100'), newStartLine: 10, newEndLine: 100, @@ -298,7 +298,7 @@ describe('createLineOutOfRangeFix', () => { }; expect(() => createLineOutOfRangeFix(error)).toThrow( - 'LINE_OUT_OF_RANGEエラーメッセージから行数を取得できません' + 'Cannot get line count from LINE_OUT_OF_RANGE error message' ); }); }); @@ -332,7 +332,7 @@ describe('createSymbolRangeMismatchFix', () => { expect(result).toEqual({ type: 'UPDATE_SYMBOL_RANGE', error, - description: 'シンボル "ClassName#methodName" の行番号を 10-20 から 15-25 に更新', + description: 'Update line numbers for symbol "ClassName#methodName" from 10-20 to 15-25', preview: expect.stringContaining('15-25'), newStartLine: 15, newEndLine: 25, @@ -347,7 +347,7 @@ describe('createSymbolRangeMismatchFix', () => { }; expect(() => createSymbolRangeMismatchFix(error)).toThrow( - 'SYMBOL_RANGE_MISMATCHにはsuggestedSymbolが必要です' + 'SYMBOL_RANGE_MISMATCH requires suggestedSymbol' ); }); }); @@ -396,7 +396,7 @@ describe('createMultipleSymbolsFoundFix', () => { expect(result).toEqual({ type: 'UPDATE_SYMBOL_RANGE', error, - description: 'シンボル "methodName" の行番号を追加: 50-60', + description: 'Add line numbers for symbol "methodName": 50-60', preview: expect.stringContaining('50-60'), newStartLine: 50, newEndLine: 60, @@ -413,7 +413,7 @@ describe('createMultipleSymbolsFoundFix', () => { const mockRl = {} as readline.Interface; await expect(createMultipleSymbolsFoundFix(error, mockRl)).rejects.toThrow( - 'MULTIPLE_SYMBOLS_FOUNDにはfoundSymbolsが必要です' + 'MULTIPLE_SYMBOLS_FOUND requires foundSymbols' ); }); }); @@ -474,7 +474,7 @@ describe('createFixAction', () => { }; await expect(createFixAction(error)).rejects.toThrow( - 'MULTIPLE_SYMBOLS_FOUNDにはreadline.Interfaceが必要です' + 'MULTIPLE_SYMBOLS_FOUND requires readline.Interface' ); }); }); diff --git a/src/utils/fix.ts b/src/utils/fix.ts index 1f87fc6..2a3de3b 100644 --- a/src/utils/fix.ts +++ b/src/utils/fix.ts @@ -1,5 +1,5 @@ /** - * 修正ロジックユーティリティ + * Fix logic utilities */ import * as fs from 'fs'; @@ -20,7 +20,7 @@ import { askSelectOption } from '@/utils/prompt'; import type { CodeRefError, ExpandedMatch, FixAction } from '@/utils/types'; /** - * エラーが修正可能かチェック + * Check if error is fixable */ export function isFixableError(error: CodeRefError): boolean { return [ @@ -34,18 +34,18 @@ export function isFixableError(error: CodeRefError): boolean { } /** - * CODE_LOCATION_MISMATCHの修正アクションを作成 + * Create fix action for CODE_LOCATION_MISMATCH */ export function createLocationMismatchFix(error: CodeRefError): FixAction { if (!error.suggestedLines) { - throw new Error('CODE_LOCATION_MISMATCHにはsuggestedLinesが必要です'); + throw new Error('CODE_LOCATION_MISMATCH requires suggestedLines'); } const { ref } = error; const oldComment = ref.fullMatch; const newComment = ``; - // 実際のコードを取得してプレビュー + // Get actual code for preview const projectRoot = path.resolve(__dirname, '../../..'); const absolutePath = path.resolve(projectRoot, ref.refPath); const actualCode = extractLinesFromFile( @@ -57,7 +57,7 @@ export function createLocationMismatchFix(error: CodeRefError): FixAction { return { type: 'UPDATE_LINE_NUMBERS', error, - description: `行番号を ${ref.startLine}-${ref.endLine} から ${error.suggestedLines.start}-${error.suggestedLines.end} に更新`, + description: `Update line numbers from ${ref.startLine}-${ref.endLine} to ${error.suggestedLines.start}-${error.suggestedLines.end}`, preview: `${oldComment}\n→ ${newComment}`, newStartLine: error.suggestedLines.start, newEndLine: error.suggestedLines.end, @@ -66,11 +66,11 @@ export function createLocationMismatchFix(error: CodeRefError): FixAction { } /** - * CODE_REFコメント近くのコードブロックを検索 + * Search for code block near CODE_REF comment * - * @param content ドキュメントの内容 - * @param commentMatch CODE_REFコメントのテキスト - * @returns コードブロックの情報、見つからない場合はnull + * @param content Document content + * @param commentMatch CODE_REF comment text + * @returns Code block information, or null if not found */ function findCodeBlockNearComment( content: string, @@ -85,11 +85,11 @@ function findCodeBlockNearComment( const searchStart = commentEnd + 3; const searchWindow = content.substring(searchStart, searchStart + 5000); - // 次のCODE_REFコメントを検索 + // Search for next CODE_REF comment const nextCommentMatch = /`; const option2: FixAction = { type: 'UPDATE_LINE_NUMBERS', error, - description: `シンボル指定を削除して行番号指定に変更(コードブロックは維持、手動調整が必要)`, + description: `Remove symbol specification and change to line number specification (maintain code block, manual adjustment needed)`, preview: `${oldComment}\n→ ${newComment}`, newStartLine: symbolMatch.startLine, newEndLine: symbolMatch.endLine, - newCodeBlock: ref.codeBlock, // 既存のコードブロックを維持 + newCodeBlock: ref.codeBlock, // Maintain existing code block }; return [option1, option2]; } } - // 行番号指定がある場合の既存ロジック + // Existing logic when line numbers are specified if (ref.startLine === null || ref.endLine === null) { - throw new Error('CODE_CONTENT_MISMATCHには行番号指定またはシンボル指定が必要です'); + throw new Error('CODE_CONTENT_MISMATCH requires line numbers or symbol specification'); } const actualCode = extractLinesFromFile(absolutePath, ref.startLine, ref.endLine); - // AST解析を使って、指定された行範囲が不完全なスコープ(関数の途中など)でないかチェック - // 開始行のみを使用してスコープを検出(範囲全体を使うと誤検出の可能性があるため) + // Check if specified line range is incomplete scope (e.g., middle of function) using AST analysis + // Detect scope using only start line (to avoid false detection when using entire range) const fileContent = fs.readFileSync(absolutePath, 'utf-8'); const expandedMatches = expandMatchToScope({ filePath: absolutePath, @@ -279,17 +279,17 @@ export function createContentMismatchFix(error: CodeRefError): FixAction | FixAc fileContent, }); - // 拡張結果が元の範囲と異なり、かつ高信頼度の場合は行番号も更新 + // Update line numbers if expansion result differs from original range and has high confidence const expanded = expandedMatches[0]; if ( expanded?.confidence === 'high' && (expanded.start !== ref.startLine || expanded.end !== ref.endLine) ) { - // スコープ拡張が成功した場合は行番号も更新 + // Update line numbers if scope expansion succeeds const expandedCode = extractLinesFromFile(absolutePath, expanded.start, expanded.end); const scopeInfo = expanded.scopeType ? ` (${expanded.scopeType})` : ''; console.log( - ` ℹ️ AST解析により ${ref.startLine}-${ref.endLine} を ${expanded.start}-${expanded.end} に拡張${scopeInfo}` + ` ℹ️ Expanded ${ref.startLine}-${ref.endLine} to ${expanded.start}-${expanded.end} using AST analysis${scopeInfo}` ); const oldComment = ref.fullMatch; @@ -298,7 +298,7 @@ export function createContentMismatchFix(error: CodeRefError): FixAction | FixAc return { type: 'UPDATE_LINE_NUMBERS', error, - description: `AST解析により行番号を ${ref.startLine}-${ref.endLine} から ${expanded.start}-${expanded.end} に更新し、コードブロックを置換`, + description: `Update line numbers from ${ref.startLine}-${ref.endLine} to ${expanded.start}-${expanded.end} using AST analysis and replace code block`, preview: `${oldComment}\n→ ${newComment}`, newStartLine: expanded.start, newEndLine: expanded.end, @@ -306,29 +306,29 @@ export function createContentMismatchFix(error: CodeRefError): FixAction | FixAc }; } - // AST拡張が不要または失敗した場合は、従来通りコードブロックのみ置換 + // If AST expansion is unnecessary or fails, replace only code block as before const expectedPreview = error.expectedCode?.substring(0, 100) || ''; const actualPreview = actualCode.substring(0, 100); return { type: 'REPLACE_CODE_BLOCK', error, - description: 'コードブロックを実際のコード内容で置換', - preview: `期待: ${expectedPreview}${expectedPreview.length >= 100 ? '...' : ''}\n実際: ${actualPreview}${actualPreview.length >= 100 ? '...' : ''}`, + description: 'Replace code block with actual code content', + preview: `Expected: ${expectedPreview}${expectedPreview.length >= 100 ? '...' : ''}\nActual: ${actualPreview}${actualPreview.length >= 100 ? '...' : ''}`, newCodeBlock: actualCode, }; } /** - * LINE_OUT_OF_RANGEの修正アクションを作成 + * Create fix action for LINE_OUT_OF_RANGE */ export function createLineOutOfRangeFix(error: CodeRefError): FixAction { const { ref } = error; - // エラーメッセージから行数を抽出 + // Extract line count from error message const match = /(\d+)\s*>\s*(\d+)/.exec(error.message); if (!match) { - throw new Error('LINE_OUT_OF_RANGEエラーメッセージから行数を取得できません'); + throw new Error('Cannot get line count from LINE_OUT_OF_RANGE error message'); } const totalLines = parseInt(match[2], 10); @@ -338,7 +338,7 @@ export function createLineOutOfRangeFix(error: CodeRefError): FixAction { return { type: 'UPDATE_END_LINE', error, - description: `終了行を ${ref.endLine} から ${totalLines} (ファイル末尾) に修正`, + description: `Fix end line from ${ref.endLine} to ${totalLines} (end of file)`, preview: `${oldComment}\n→ ${newComment}`, newStartLine: ref.startLine!, newEndLine: totalLines, @@ -346,11 +346,11 @@ export function createLineOutOfRangeFix(error: CodeRefError): FixAction { } /** - * SYMBOL_RANGE_MISMATCHの修正アクションを作成 + * Create fix action for SYMBOL_RANGE_MISMATCH */ export function createSymbolRangeMismatchFix(error: CodeRefError): FixAction { if (!error.suggestedSymbol) { - throw new Error('SYMBOL_RANGE_MISMATCHにはsuggestedSymbolが必要です'); + throw new Error('SYMBOL_RANGE_MISMATCH requires suggestedSymbol'); } const { ref } = error; @@ -362,7 +362,7 @@ export function createSymbolRangeMismatchFix(error: CodeRefError): FixAction { return { type: 'UPDATE_SYMBOL_RANGE', error, - description: `シンボル "${ref.symbolPath}" の行番号を ${ref.startLine}-${ref.endLine} から ${suggested.startLine}-${suggested.endLine} に更新`, + description: `Update line numbers for symbol "${ref.symbolPath}" from ${ref.startLine}-${ref.endLine} to ${suggested.startLine}-${suggested.endLine}`, preview: `${oldComment}\n→ ${newComment}`, newStartLine: suggested.startLine, newEndLine: suggested.endLine, @@ -370,27 +370,27 @@ export function createSymbolRangeMismatchFix(error: CodeRefError): FixAction { } /** - * MULTIPLE_SYMBOLS_FOUNDの修正アクションを作成(対話的に選択) + * Create fix action for MULTIPLE_SYMBOLS_FOUND (interactive selection) */ export async function createMultipleSymbolsFoundFix( error: CodeRefError, rl: readline.Interface ): Promise { if (!error.foundSymbols || error.foundSymbols.length === 0) { - throw new Error('MULTIPLE_SYMBOLS_FOUNDにはfoundSymbolsが必要です'); + throw new Error('MULTIPLE_SYMBOLS_FOUND requires foundSymbols'); } const { ref } = error; const options = error.foundSymbols.map((symbol) => { const classInfo = symbol.className ? `${symbol.className}#` : ''; - return `行 ${symbol.startLine}-${symbol.endLine} (${classInfo}${symbol.memberName})`; + return `Line ${symbol.startLine}-${symbol.endLine} (${classInfo}${symbol.memberName})`; }); const selectedIndex = await askSelectOption( rl, options, - `⚠️ シンボル "${ref.symbolPath}" が${error.foundSymbols.length}箇所で見つかりました。どの位置を使用しますか?` + `⚠️ Symbol "${ref.symbolPath}" found in ${error.foundSymbols.length} locations. Which position should be used?` ); const selected = error.foundSymbols[selectedIndex]; @@ -401,7 +401,7 @@ export async function createMultipleSymbolsFoundFix( return { type: 'UPDATE_SYMBOL_RANGE', error, - description: `シンボル "${ref.symbolPath}" の行番号を追加: ${selected.startLine}-${selected.endLine}`, + description: `Add line numbers for symbol "${ref.symbolPath}": ${selected.startLine}-${selected.endLine}`, preview: `${oldComment}\n→ ${newComment}`, newStartLine: selected.startLine, newEndLine: selected.endLine, @@ -409,7 +409,7 @@ export async function createMultipleSymbolsFoundFix( } /** - * エラータイプに基づいて修正アクションを作成 + * Create fix action based on error type */ export async function createFixAction( error: CodeRefError, @@ -432,7 +432,7 @@ export async function createFixAction( return createSymbolRangeMismatchFix(error); case 'MULTIPLE_SYMBOLS_FOUND': if (!rl) { - throw new Error('MULTIPLE_SYMBOLS_FOUNDにはreadline.Interfaceが必要です'); + throw new Error('MULTIPLE_SYMBOLS_FOUND requires readline.Interface'); } return await createMultipleSymbolsFoundFix(error, rl); default: @@ -441,8 +441,8 @@ export async function createFixAction( } /** - * マークダウンファイルに修正を適用 - * @returns ライン delta(正の数は行追加、負の数は行削除、0は行数変化なし) + * Apply fix to markdown file + * @returns Line delta (positive: lines added, negative: lines removed, 0: no change) */ export function applyFix(action: FixAction): number { const filePath = action.error.ref.docFile; @@ -452,12 +452,12 @@ export function applyFix(action: FixAction): number { switch (action.type) { case 'UPDATE_LINE_NUMBERS': case 'UPDATE_END_LINE': { - // CODE_REFコメントを更新 + // Update CODE_REF comment const oldComment = action.error.ref.fullMatch; const newComment = ``; content = replaceCodeRefComment(content, oldComment, newComment); - // コードブロックも更新(存在する場合) + // Also update code block (if exists) if (action.newCodeBlock) { const blockPos = findCodeBlockPosition(content, newComment); if (blockPos) { @@ -480,7 +480,7 @@ export function applyFix(action: FixAction): number { case 'REPLACE_CODE_BLOCK': { if (!action.error.ref.codeBlock) { - throw new Error('コードブロックが見つかりません'); + throw new Error('Code block not found'); } content = replaceCodeBlock(content, action.error.ref.codeBlock, action.newCodeBlock!); break; @@ -488,7 +488,7 @@ export function applyFix(action: FixAction): number { case 'MOVE_CODE_REF_COMMENT': { if (!action.codeBlockPosition) { - throw new Error('MOVE_CODE_REF_COMMENTにはcodeBlockPositionが必要です'); + throw new Error('MOVE_CODE_REF_COMMENT requires codeBlockPosition'); } content = moveCodeRefCommentBeforeCodeBlock( @@ -502,13 +502,13 @@ export function applyFix(action: FixAction): number { fs.writeFileSync(filePath, content, 'utf-8'); - // ライン deltaを返す + // Return line delta const newLines = content.split('\n').length; return newLines - originalLines; } /** - * 優先順位付けのコンテキスト + * Prioritization context */ interface PrioritizationContext { originalStart: number; @@ -516,29 +516,29 @@ interface PrioritizationContext { } /** - * 信頼度とスコープタイプを考慮した優先順位付け + * Prioritization considering confidence and scope type * - * @param matches 拡張されたマッチの配列 - * @param context 優先順位付けのコンテキスト(元の行番号) - * @returns 優先順位付けされたマッチ(信頼度・スコープタイプ考慮) + * @param matches Array of expanded matches + * @param context Prioritization context (original line numbers) + * @returns Prioritized matches (considering confidence and scope type) */ function prioritizeMatchesWithConfidence( matches: ExpandedMatch[], context: PrioritizationContext ): ExpandedMatch[] { return matches.sort((a, b) => { - // 1. 信頼度で優先(最優先) + // 1. Prioritize by confidence (highest priority) const confidenceScore = { high: 3, medium: 2, low: 1 }; const confDiff = confidenceScore[b.confidence] - confidenceScore[a.confidence]; if (confDiff !== 0) return confDiff; - // 2. 元の行番号との近接度で優先 + // 2. Prioritize by proximity to original line numbers const distanceA = Math.abs(a.start - context.originalStart); const distanceB = Math.abs(b.start - context.originalStart); const distDiff = distanceA - distanceB; if (distDiff !== 0) return distDiff; - // 3. スコープタイプの優先順位 + // 3. Scope type priority const scopePriority = { interface: 5, type: 4, @@ -551,13 +551,13 @@ function prioritizeMatchesWithConfidence( scopePriority[b.scopeType || 'unknown'] - scopePriority[a.scopeType || 'unknown']; if (scopeDiff !== 0) return scopeDiff; - // 4. 短い範囲を優先(より具体的なマッチ) + // 4. Prioritize shorter range (more specific match) return a.end - a.start - (b.end - b.start); }); } /** - * 複数マッチの処理 + * Handle multiple matches */ export async function handleMultipleMatches( error: CodeRefError, @@ -572,7 +572,7 @@ export async function handleMultipleMatches( const projectRoot = path.resolve(__dirname, '../../..'); const absolutePath = path.resolve(projectRoot, ref.refPath); - // AST解析によるスコープ拡張版を使用 + // Use AST analysis with scope expansion const matches = searchCodeInFileWithScopeExpansion(absolutePath, ref.codeBlock); if (matches.length === 0) { @@ -580,21 +580,21 @@ export async function handleMultipleMatches( } if (matches.length === 1) { - // 単一マッチ - 修正アクションを作成 + // Single match - create fix action const match = matches[0]; const confidenceInfo = match.confidence === 'high' && match.scopeType ? ` (${match.scopeType})` : match.confidence === 'low' - ? ' (低信頼度)' + ? ' (low confidence)' : ''; console.log( - `\n✓ ${ref.refPath} で単一マッチを検出: 行 ${match.start}-${match.end}${confidenceInfo}` + `\n✓ Detected single match in ${ref.refPath}: lines ${match.start}-${match.end}${confidenceInfo}` ); return { type: 'UPDATE_LINE_NUMBERS', error, - description: `行番号を ${ref.startLine}-${ref.endLine} から ${match.start}-${match.end} に更新`, + description: `Update line numbers from ${ref.startLine}-${ref.endLine} to ${match.start}-${match.end}`, preview: ``, newStartLine: match.start, newEndLine: match.end, @@ -602,24 +602,24 @@ export async function handleMultipleMatches( }; } - // 複数マッチ - 優先順位付けして自動選択を試みる + // Multiple matches - try automatic selection with prioritization const sortedMatches = prioritizeMatchesWithConfidence(matches, { originalStart: ref.startLine!, originalEnd: ref.endLine!, }); - // 信頼度が高いマッチが1つだけなら自動選択 + // Auto-select if only one high-confidence match const highConfidenceMatches = sortedMatches.filter((m) => m.confidence === 'high'); if (highConfidenceMatches.length === 1) { const bestMatch = highConfidenceMatches[0]; const scopeInfo = bestMatch.scopeType ? ` (${bestMatch.scopeType})` : ''; console.log( - `\n✓ ${ref.refPath} で最も適切なマッチを自動選択: 行 ${bestMatch.start}-${bestMatch.end}${scopeInfo}` + `\n✓ Auto-selected most appropriate match in ${ref.refPath}: lines ${bestMatch.start}-${bestMatch.end}${scopeInfo}` ); return { type: 'UPDATE_LINE_NUMBERS', error, - description: `行番号を ${ref.startLine}-${ref.endLine} から ${bestMatch.start}-${bestMatch.end} に更新`, + description: `Update line numbers from ${ref.startLine}-${ref.endLine} to ${bestMatch.start}-${bestMatch.end}`, preview: ``, newStartLine: bestMatch.start, newEndLine: bestMatch.end, @@ -627,30 +627,30 @@ export async function handleMultipleMatches( }; } - // それ以外はユーザーに選択させる - console.log(`\n⚠️ ${ref.refPath} でコードが ${matches.length} 箇所見つかりました:`); + // Otherwise let user choose + console.log(`\n⚠️ Code found in ${matches.length} locations in ${ref.refPath}:`); const options = sortedMatches.map((m) => { const confidenceLabel = - m.confidence === 'high' ? '高' : m.confidence === 'medium' ? '中' : '低'; + m.confidence === 'high' ? 'high' : m.confidence === 'medium' ? 'medium' : 'low'; const scopeInfo = m.scopeType && m.scopeType !== 'unknown' ? `, ${m.scopeType}` : ''; - return `行 ${m.start}-${m.end} (信頼度: ${confidenceLabel}${scopeInfo})`; + return `Line ${m.start}-${m.end} (confidence: ${confidenceLabel}${scopeInfo})`; }); - const selection = await askSelectOption(rl, options, 'どの位置を使用しますか?'); + const selection = await askSelectOption(rl, options, 'Which position should be used?'); const selectedMatch = sortedMatches[selection]; - // 選択されたマッチの信頼度が低い場合は警告 + // Warn if selected match has low confidence if (selectedMatch.confidence === 'low') { - console.warn('⚠️ スコープ検出の信頼度が低いため、結果を確認してください。'); + console.warn('⚠️ Scope detection confidence is low, please verify the result.'); } if (selectedMatch.expansionType === 'none') { - console.warn('⚠️ 構造的な解析ができなかったため、単純な文字列マッチングを使用しています。'); + console.warn('⚠️ Structural analysis failed, using simple string matching.'); } return { type: 'UPDATE_LINE_NUMBERS', error, - description: `行番号を ${ref.startLine}-${ref.endLine} から ${selectedMatch.start}-${selectedMatch.end} に更新`, + description: `Update line numbers from ${ref.startLine}-${ref.endLine} to ${selectedMatch.start}-${selectedMatch.end}`, preview: ``, newStartLine: selectedMatch.start, newEndLine: selectedMatch.end, From 79f609e0bc22eab31bbe38a69d01e1a54e7e099b Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Tue, 30 Dec 2025 23:21:35 +0900 Subject: [PATCH 29/57] feat: translate comments and messages to english in src/core/validate.ts and tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/core/validate.test.ts | 12 ++-- src/core/validate.ts | 120 +++++++++++++++++++------------------- 2 files changed, 66 insertions(+), 66 deletions(-) diff --git a/src/core/validate.test.ts b/src/core/validate.test.ts index d704825..7674149 100644 --- a/src/core/validate.test.ts +++ b/src/core/validate.test.ts @@ -199,7 +199,7 @@ describe('validate-docs-code', () => { expect(result).toHaveLength(1); expect(result[0].type).toBe('FILE_NOT_FOUND'); - expect(result[0].message).toBe('参照先のファイルが見つかりません: src/missing.ts'); + expect(result[0].message).toBe('Referenced file not found: src/missing.ts'); }); it('パストラバーサルでPATH_TRAVERSALエラーを返すこと', () => { @@ -294,7 +294,7 @@ describe('validate-docs-code', () => { expect(result).toHaveLength(1); expect(result[0].type).toBe('READ_ERROR'); - expect(result[0].message).toBe('ファイルの読み込みに失敗しました: Permission denied'); + expect(result[0].message).toBe('Failed to read file: Permission denied'); }); it('有効な行範囲指定では空のエラー配列を返すこと(コードブロックがない場合)', () => { @@ -487,7 +487,7 @@ line4`; expect(result).toHaveLength(1); expect(result[0].type).toBe('CODE_CONTENT_MISMATCH'); expect(result[0].message).toContain('src/example.ts'); - expect(result[0].message).toContain('コードが一致しません'); + expect(result[0].message).toContain('Code does not match'); }); it('CODE_BLOCK_MISSING: CODE_REFの後にコードブロックがない場合', () => { @@ -506,7 +506,7 @@ line4`; expect(result).toHaveLength(1); expect(result[0].type).toBe('CODE_BLOCK_MISSING'); - expect(result[0].message).toContain('の後にコードブロックが見つかりません'); + expect(result[0].message).toContain('Code block not found after CODE_REF'); expect(result[0].message).toContain('src/example.ts'); }); @@ -541,7 +541,7 @@ line12`; // ここでは CODE_LOCATION_MISMATCH をチェック const locationMismatch = result.find((e) => e.type === 'CODE_LOCATION_MISMATCH'); expect(locationMismatch).toBeDefined(); - expect(locationMismatch!.message).toContain('コードは3箇所で見つかりました'); + expect(locationMismatch!.message).toContain('Code found in 3 locations'); expect(locationMismatch!.message).toContain('result: 1-2'); expect(locationMismatch!.suggestedLines).toEqual({ start: 1, end: 2 }); }); @@ -626,7 +626,7 @@ line4`; expect(result).toHaveLength(1); expect(result[0].type).toBe('CODE_BLOCK_MISSING'); - expect(result[0].message).toContain('シンボル指定のCODE_REF'); + expect(result[0].message).toContain('CODE_REF with symbol specification'); expect(result[0].message).toContain('src/example.ts#myFunction'); }); diff --git a/src/core/validate.ts b/src/core/validate.ts index 38bd050..80f1644 100644 --- a/src/core/validate.ts +++ b/src/core/validate.ts @@ -20,11 +20,11 @@ import { associateCodeBlocksWithRefs } from '@/utils/markdown'; import type { CodeRef, CodeRefError } from '@/utils/types'; import { loadConfig, type CodeRefConfig } from '@/config'; -// CODE_REF パターン定数 +// CODE_REF pattern constant const CODE_REF_PATTERN = //g; /** - * ディレクトリを再帰的に走査してマークダウンファイルを取得 + * Recursively walk directory to get markdown files */ export function findMarkdownFiles(dir: string): string[] { const files: string[] = []; @@ -48,12 +48,12 @@ export function findMarkdownFiles(dir: string): string[] { } /** - * コードブロックとインラインコードの範囲を検出 + * Detect code block and inline code ranges */ function getCodeBlockRanges(content: string): { start: number; end: number }[] { const ranges: { start: number; end: number }[] = []; - // トリプルバッククォートのコードブロック + // Triple backtick code blocks const codeBlockPattern = /```[\s\S]*?```/g; let match: RegExpExecArray | null; @@ -64,7 +64,7 @@ function getCodeBlockRanges(content: string): { start: number; end: number }[] { }); } - // インラインコード(バッククォート) + // Inline code (backticks) const inlineCodePattern = /`[^`\n]+?`/g; while ((match = inlineCodePattern.exec(content)) !== null) { ranges.push({ @@ -77,35 +77,35 @@ function getCodeBlockRanges(content: string): { start: number; end: number }[] { } /** - * 位置がコードブロックまたはインラインコード内かチェック + * Check if position is inside code block or inline code */ function isInsideCodeBlock(position: number, ranges: { start: number; end: number }[]): boolean { return ranges.some((range) => position >= range.start && position < range.end); } /** - * CODE_REFコメントを抽出 + * Extract CODE_REF comments */ export function extractCodeRefs(content: string, filePath: string): CodeRef[] { const refs: CodeRef[] = []; let match: RegExpExecArray | null; - // コードブロックとインラインコードの範囲を事前に検出 + // Pre-detect code block and inline code ranges const codeBlockRanges = getCodeBlockRanges(content); while ((match = CODE_REF_PATTERN.exec(content)) !== null) { const [fullMatch, refPath, symbolPath, startLine, endLine] = match; - // コードブロックまたはインラインコード内のCODE_REFは除外(サンプル表示用) + // Exclude CODE_REFs inside code blocks or inline code (for sample display) if (isInsideCodeBlock(match.index, codeBlockRanges)) { continue; } - // マッチ位置から行番号を計算(1-indexed) + // Calculate line number from match position (1-indexed) const beforeMatch = content.substring(0, match.index); const docLineNumber = beforeMatch.split('\n').length; - // シンボルパスをパース + // Parse symbol path let className: string | undefined; let memberName: string | undefined; if (symbolPath) { @@ -121,40 +121,40 @@ export function extractCodeRefs(content: string, filePath: string): CodeRef[] { endLine: endLine ? parseInt(endLine, 10) : null, docFile: filePath, docLineNumber, - codeBlockStartOffset: match.index, // CODE_REFコメントの位置を保存 + codeBlockStartOffset: match.index, // Save CODE_REF comment position symbolPath: symbolPath?.trim(), className, memberName, }); } - // コードブロックを関連付け + // Associate code blocks return associateCodeBlocksWithRefs(content, refs); } /** - * コード内容の検証 + * Validate code content */ export function validateCodeContent(ref: CodeRef, config?: CodeRefConfig): CodeRefError[] { const cfg = config || loadConfig(); const projectRoot = cfg.projectRoot; const errors: CodeRefError[] = []; - // 行数指定がない場合の処理 + // Process when line numbers not specified if (ref.startLine === null || ref.endLine === null) { - // シンボル指定がある場合は、シンボル全体との完全一致検証 + // When symbol specified, validate exact match with entire symbol if (ref.symbolPath) { - // コードブロックがない場合はエラーを返す + // Return error if no code block if (!ref.codeBlock || ref.codeBlock.trim() === '') { errors.push({ type: 'CODE_BLOCK_MISSING', - message: `シンボル指定のCODE_REF(${ref.refPath}#${ref.symbolPath})の後にコードブロックが見つかりません。`, + message: `Code block not found after CODE_REF with symbol specification (${ref.refPath}#${ref.symbolPath}).`, ref, }); return errors; } - // シンボルの範囲を取得 + // Get symbol range const absolutePath = path.resolve(projectRoot, ref.refPath); const fileContent = fs.readFileSync(absolutePath, 'utf-8'); const matches = findSymbolInAST(fileContent, absolutePath, { @@ -170,11 +170,11 @@ export function validateCodeContent(ref: CodeRef, config?: CodeRefConfig): CodeR symbolMatch.endLine ); - // コードブロックとシンボル全体を比較(空白改行は無視) + // Compare code block with entire symbol (ignore whitespace and newlines) if (!compareCodeContent(symbolCode, ref.codeBlock)) { errors.push({ type: 'CODE_CONTENT_MISMATCH', - message: `シンボル全体とコードブロックが一致しません。`, + message: `Code block does not match entire symbol.`, ref, actualCode: symbolCode.substring(0, 200), expectedCode: ref.codeBlock.substring(0, 200), @@ -183,19 +183,19 @@ export function validateCodeContent(ref: CodeRef, config?: CodeRefConfig): CodeR } return errors; } - // シンボル指定がない場合はスキップ(ファイル全体参照) + // Skip if no symbol specified (whole file reference) return errors; } - // シンボル指定がある場合でも、行番号指定があれば通常の検証を続行 + // Continue normal validation if line numbers specified, even with symbol specification - // コードブロックがない場合 + // If no code block if (!ref.codeBlock || ref.codeBlock.trim() === '') { const refLocation = ref.startLine !== null ? `${ref.refPath}:${ref.startLine}-${ref.endLine}` : ref.refPath; errors.push({ type: 'CODE_BLOCK_MISSING', - message: `CODE_REF(${refLocation})の後にコードブロックが見つかりません。`, + message: `Code block not found after CODE_REF (${refLocation}).`, ref, }); return errors; @@ -204,31 +204,31 @@ export function validateCodeContent(ref: CodeRef, config?: CodeRefConfig): CodeR const absolutePath = path.resolve(projectRoot, ref.refPath); try { - // 実ファイルから指定行のコードを取得 + // Get code from actual file at specified lines const actualCode = extractLinesFromFile(absolutePath, ref.startLine, ref.endLine); - // コード内容を比較 + // Compare code content if (!compareCodeContent(actualCode, ref.codeBlock)) { - // 一致しない → ファイル全体から検索 + // Not matched → search entire file const matches = searchCodeInFile(absolutePath, ref.codeBlock); if (matches.length > 0) { - // コードは存在するが別の場所にある + // Code exists but at different location const firstMatch = matches[0]; const matchInfo = - matches.length > 1 ? `コードは${matches.length}箇所で見つかりました。最初の出現: ` : ''; + matches.length > 1 ? `Code found in ${matches.length} locations. First occurrence: ` : ''; errors.push({ type: 'CODE_LOCATION_MISMATCH', - message: `${ref.refPath}の行数が一致しません。${matchInfo}(expect: ${ref.startLine}-${ref.endLine}, result: ${firstMatch.start}-${firstMatch.end})`, + message: `Line numbers do not match in ${ref.refPath}. ${matchInfo}(expect: ${ref.startLine}-${ref.endLine}, result: ${firstMatch.start}-${firstMatch.end})`, ref, suggestedLines: firstMatch, }); } else { - // コード内容が異なる + // Code content differs errors.push({ type: 'CODE_CONTENT_MISMATCH', - message: `${ref.refPath} のコードが一致しません。`, + message: `Code does not match in ${ref.refPath}.`, ref, actualCode: actualCode.substring(0, 200), expectedCode: ref.codeBlock.substring(0, 200), @@ -239,7 +239,7 @@ export function validateCodeContent(ref: CodeRef, config?: CodeRefConfig): CodeR const errorMessage = error instanceof Error ? error.message : String(error); errors.push({ type: 'READ_ERROR', - message: `コード比較中にエラーが発生しました: ${errorMessage}`, + message: `Error occurred during code comparison: ${errorMessage}`, ref, }); } @@ -248,25 +248,25 @@ export function validateCodeContent(ref: CodeRef, config?: CodeRefConfig): CodeR } /** - * シンボル指定の検証 + * Validate symbol specification */ export function validateSymbolRef(ref: CodeRef, config?: CodeRefConfig): CodeRefError[] { const cfg = config || loadConfig(); const projectRoot = cfg.projectRoot; const errors: CodeRefError[] = []; - // シンボル指定がない場合はスキップ + // Skip if no symbol specification if (!ref.symbolPath) { return errors; } const absolutePath = path.resolve(projectRoot, ref.refPath); - // TypeScript/JavaScriptファイルチェック + // TypeScript/JavaScript file check if (!isTypeScriptOrJavaScript(absolutePath)) { errors.push({ type: 'NOT_TYPESCRIPT_FILE', - message: `シンボル指定はTypeScript/JavaScriptファイルのみサポート: ${ref.refPath}`, + message: `Symbol specification only supported for TypeScript/JavaScript files: ${ref.refPath}`, ref, }); return errors; @@ -275,29 +275,29 @@ export function validateSymbolRef(ref: CodeRef, config?: CodeRefConfig): CodeRef const fileContent = fs.readFileSync(absolutePath, 'utf-8'); try { - // シンボルを検索 + // Search for symbol const matches = findSymbolInAST(fileContent, absolutePath, { className: ref.className, memberName: ref.memberName!, }); if (matches.length === 0) { - // シンボルが見つからない + // Symbol not found errors.push({ type: 'SYMBOL_NOT_FOUND', - message: `シンボル "${ref.symbolPath}" が見つかりません`, + message: `Symbol "${ref.symbolPath}" not found`, ref, }); } else if (matches.length > 1 && !ref.startLine) { - // 複数マッチ(行番号ヒントなし) + // Multiple matches (no line number hint) errors.push({ type: 'MULTIPLE_SYMBOLS_FOUND', - message: `シンボル "${ref.symbolPath}" が${matches.length}箇所で見つかりました。行番号を指定してください`, + message: `Symbol "${ref.symbolPath}" found in ${matches.length} locations. Please specify line numbers`, ref, foundSymbols: matches, }); } else { - // 行番号が指定されている場合は検証 + // Validate if line numbers specified if (ref.startLine && ref.endLine) { const bestMatch = selectBestSymbolMatch(matches, { start: ref.startLine, @@ -305,11 +305,11 @@ export function validateSymbolRef(ref: CodeRef, config?: CodeRefConfig): CodeRef }); if (bestMatch) { - // 範囲が一致するかチェック + // Check if range matches if (bestMatch.startLine !== ref.startLine || bestMatch.endLine !== ref.endLine) { errors.push({ type: 'SYMBOL_RANGE_MISMATCH', - message: `シンボル "${ref.symbolPath}" の範囲が一致しません (期待: ${ref.startLine}-${ref.endLine}, 実際: ${bestMatch.startLine}-${bestMatch.endLine})`, + message: `Symbol "${ref.symbolPath}" range does not match (expected: ${ref.startLine}-${ref.endLine}, actual: ${bestMatch.startLine}-${bestMatch.endLine})`, ref, suggestedSymbol: bestMatch, }); @@ -320,7 +320,7 @@ export function validateSymbolRef(ref: CodeRef, config?: CodeRefConfig): CodeRef } catch (error) { errors.push({ type: 'READ_ERROR', - message: `AST解析エラー: ${error instanceof Error ? error.message : String(error)}`, + message: `AST parsing error: ${error instanceof Error ? error.message : String(error)}`, ref, }); } @@ -329,37 +329,37 @@ export function validateSymbolRef(ref: CodeRef, config?: CodeRefConfig): CodeRef } /** - * 参照先のファイルと行番号の存在を確認 + * Validate existence of referenced file and line numbers */ export function validateCodeRef(ref: CodeRef, config?: CodeRefConfig): CodeRefError[] { const cfg = config || loadConfig(); const projectRoot = cfg.projectRoot; const errors: CodeRefError[] = []; - // 相対パスを絶対パスに変換(プロジェクトルートからの相対パス) + // Convert relative path to absolute path (relative to project root) const absolutePath = path.resolve(projectRoot, ref.refPath); - // パストラバーサル攻撃を防ぐ: プロジェクトルート内に留まるか検証 + // Prevent path traversal attacks: validate stays within project root if (!absolutePath.startsWith(projectRoot + path.sep)) { errors.push({ type: 'PATH_TRAVERSAL', - message: `参照先のパスがプロジェクトルート外を指しています: ${ref.refPath}`, + message: `Referenced path points outside project root: ${ref.refPath}`, ref, }); return errors; } - // ファイルの存在確認 + // Check file existence if (!fs.existsSync(absolutePath)) { errors.push({ type: 'FILE_NOT_FOUND', - message: `参照先のファイルが見つかりません: ${ref.refPath}`, + message: `Referenced file not found: ${ref.refPath}`, ref, }); return errors; } - // 行番号が指定されている場合、行数をチェック + // Check line count if line numbers specified if (ref.startLine !== null && ref.endLine !== null) { try { const content = fs.readFileSync(absolutePath, 'utf-8'); @@ -369,7 +369,7 @@ export function validateCodeRef(ref: CodeRef, config?: CodeRefConfig): CodeRefEr if (ref.startLine < 1) { errors.push({ type: 'INVALID_LINE_NUMBER', - message: `開始行番号が無効です(1未満): ${ref.startLine}`, + message: `Start line number is invalid (less than 1): ${ref.startLine}`, ref, }); } @@ -377,7 +377,7 @@ export function validateCodeRef(ref: CodeRef, config?: CodeRefConfig): CodeRefEr if (ref.endLine > totalLines) { errors.push({ type: 'LINE_OUT_OF_RANGE', - message: `終了行番号がファイルの行数を超えています: ${ref.endLine} > ${totalLines}`, + message: `End line number exceeds file line count: ${ref.endLine} > ${totalLines}`, ref, }); } @@ -385,7 +385,7 @@ export function validateCodeRef(ref: CodeRef, config?: CodeRefConfig): CodeRefEr if (ref.startLine > ref.endLine) { errors.push({ type: 'INVALID_RANGE', - message: `開始行番号が終了行番号より大きいです: ${ref.startLine} > ${ref.endLine}`, + message: `Start line number is greater than end line number: ${ref.startLine} > ${ref.endLine}`, ref, }); } @@ -393,19 +393,19 @@ export function validateCodeRef(ref: CodeRef, config?: CodeRefConfig): CodeRefEr const errorMessage = error instanceof Error ? error.message : String(error); errors.push({ type: 'READ_ERROR', - message: `ファイルの読み込みに失敗しました: ${errorMessage}`, + message: `Failed to read file: ${errorMessage}`, ref, }); } } - // シンボル指定がある場合はシンボルバリデーション + // Symbol validation if symbol specified if (errors.length === 0 && ref.symbolPath) { const symbolErrors = validateSymbolRef(ref, cfg); errors.push(...symbolErrors); } - // コード内容の検証(既存のエラーがない場合のみ) + // Validate code content (only if no existing errors) if (errors.length === 0) { const contentErrors = validateCodeContent(ref, cfg); errors.push(...contentErrors); From 68c57babfdd5ee847af57d29b47ac28168e456cf Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Tue, 30 Dec 2025 23:22:59 +0900 Subject: [PATCH 30/57] fix: translate remaining japanese character to english in src/utils/fix.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/utils/fix.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/fix.ts b/src/utils/fix.ts index 2a3de3b..6566a24 100644 --- a/src/utils/fix.ts +++ b/src/utils/fix.ts @@ -240,7 +240,7 @@ export function createContentMismatchFix(error: CodeRefError): FixAction | FixAc description: `Replace code block with entire symbol (lines ${symbolMatch.startLine}-${symbolMatch.endLine}, ${symbolMatch.endLine - symbolMatch.startLine + 1} lines)`, preview: symbolCode.length > 300 - ? `\`\`\`typescript\n${symbolCode.substring(0, 300)}...\n\`\`\` (${symbolMatch.endLine - symbolMatch.startLine + 1}行)` + ? `\`\`\`typescript\n${symbolCode.substring(0, 300)}...\n\`\`\` (${symbolMatch.endLine - symbolMatch.startLine + 1} lines)` : `\`\`\`typescript\n${symbolCode}\n\`\`\``, newCodeBlock: symbolCode, }; From 97724b3467d61b67917a0c8c4446dd8485482960 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Wed, 31 Dec 2025 00:25:18 +0900 Subject: [PATCH 31/57] fix: update project root to use config --- src/cli/fix.ts | 6 +++--- src/utils/fix.test.ts | 42 +++++++++++++++++++++++++--------------- src/utils/fix.ts | 28 ++++++++++++++++----------- src/utils/prompt.test.ts | 13 +++++++------ src/utils/prompt.ts | 3 +-- 5 files changed, 54 insertions(+), 38 deletions(-) diff --git a/src/cli/fix.ts b/src/cli/fix.ts index 91b6835..2048a48 100644 --- a/src/cli/fix.ts +++ b/src/cli/fix.ts @@ -134,9 +134,9 @@ export async function main(): Promise { if (error.type === 'CODE_LOCATION_MISMATCH') { // Handle multiple matches - action = await handleMultipleMatches(error, rl); + action = await handleMultipleMatches(error, rl, config); } else { - const fixActionResult = await createFixAction(error, rl); + const fixActionResult = await createFixAction(error, config, rl); // If there are multiple options, let the user choose if (Array.isArray(fixActionResult)) { @@ -188,7 +188,7 @@ export async function main(): Promise { // Display preview (only for single option) if (!Array.isArray(action)) { - displayFixPreview(action); + displayFixPreview(action, config.projectRoot); } // Confirmation diff --git a/src/utils/fix.test.ts b/src/utils/fix.test.ts index 1f281c8..b6d4583 100644 --- a/src/utils/fix.test.ts +++ b/src/utils/fix.test.ts @@ -3,6 +3,7 @@ */ import * as fs from 'fs'; +import * as path from 'path'; import type * as readline from 'readline'; import { extractLinesFromFile, searchCodeInFileWithScopeExpansion } from '@/utils/code-comparison'; @@ -21,6 +22,7 @@ import { import * as markdownEdit from '@/utils/markdown-edit'; import * as prompt from '@/utils/prompt'; import type { CodeRefError, FixAction } from '@/utils/types'; +import type { CodeRefConfig } from '@/config'; // モック設定 jest.mock('fs'); @@ -44,6 +46,14 @@ const mockSearchCodeInFileWithScopeExpansion = const mockMarkdownEdit = markdownEdit as jest.Mocked; const mockPrompt = prompt as jest.Mocked; +// Mock config for tests +const mockConfig: CodeRefConfig = { + projectRoot: path.resolve(__dirname, '../../..'), + docsDir: 'docs', + ignoreFile: '.docsignore', + verbose: false, +}; + describe('isFixableError', () => { it('修正可能なエラータイプの場合にtrueを返すこと', () => { const fixableTypes = [ @@ -101,7 +111,7 @@ describe('createLocationMismatchFix', () => { mockExtractLinesFromFile.mockReturnValue('code content'); - const result = createLocationMismatchFix(error); + const result = createLocationMismatchFix(error, mockConfig); expect(result).toEqual({ type: 'UPDATE_LINE_NUMBERS', @@ -128,7 +138,7 @@ describe('createLocationMismatchFix', () => { }, }; - expect(() => createLocationMismatchFix(error)).toThrow( + expect(() => createLocationMismatchFix(error, mockConfig)).toThrow( 'CODE_LOCATION_MISMATCH requires suggestedLines' ); }); @@ -159,7 +169,7 @@ describe('createBlockMissingFix', () => { ); mockExtractLinesFromFile.mockReturnValue('code content'); - const result = createBlockMissingFix(error); + const result = createBlockMissingFix(error, mockConfig); expect(result).toEqual({ type: 'INSERT_CODE_BLOCK', @@ -184,7 +194,7 @@ describe('createBlockMissingFix', () => { }, }; - expect(() => createBlockMissingFix(error)).toThrow( + expect(() => createBlockMissingFix(error, mockConfig)).toThrow( 'Whole file reference does not need code block' ); }); @@ -218,7 +228,7 @@ describe('createContentMismatchFix', () => { const astScopeExpansion = require('./ast-scope-expansion'); // eslint-disable-line astScopeExpansion.expandMatchToScope = jest.fn().mockReturnValue([]); - const result = createContentMismatchFix(error) as FixAction; + const result = createContentMismatchFix(error, mockConfig) as FixAction; expect(result.type).toBe('REPLACE_CODE_BLOCK'); expect(result.newCodeBlock).toBe('actual code'); @@ -254,7 +264,7 @@ describe('createContentMismatchFix', () => { }, ]); - const result = createContentMismatchFix(error) as FixAction; + const result = createContentMismatchFix(error, mockConfig) as FixAction; expect(result.type).toBe('UPDATE_LINE_NUMBERS'); expect(result.newStartLine).toBe(8); @@ -430,7 +440,7 @@ describe('createFixAction', () => { ref: {} as any, }; - const result = await createFixAction(error); + const result = await createFixAction(error, mockConfig); expect(result).toBeNull(); }); @@ -452,7 +462,7 @@ describe('createFixAction', () => { mockExtractLinesFromFile.mockReturnValue('code content'); - const result = (await createFixAction(error)) as FixAction; + const result = (await createFixAction(error, mockConfig)) as FixAction; expect(result?.type).toBe('UPDATE_LINE_NUMBERS'); }); @@ -473,7 +483,7 @@ describe('createFixAction', () => { ], }; - await expect(createFixAction(error)).rejects.toThrow( + await expect(createFixAction(error, mockConfig)).rejects.toThrow( 'MULTIPLE_SYMBOLS_FOUND requires readline.Interface' ); }); @@ -521,7 +531,7 @@ describe('優先順位付けロジック', () => { { start: 15, end: 25, confidence: 'high', expansionType: 'ast', scopeType: 'function' }, ]); - const result = await handleMultipleMatches(error, rl); + const result = await handleMultipleMatches(error, rl, mockConfig); expect(result).not.toBeNull(); expect(result?.newStartLine).toBe(15); @@ -545,7 +555,7 @@ describe('優先順位付けロジック', () => { { start: 100, end: 110, confidence: 'low', expansionType: 'none', scopeType: 'unknown' }, ]); - const result = await handleMultipleMatches(error, rl); + const result = await handleMultipleMatches(error, rl, mockConfig); expect(result).not.toBeNull(); expect(result?.newStartLine).toBe(10); @@ -565,7 +575,7 @@ describe('優先順位付けロジック', () => { { start: 50, end: 60, confidence: 'medium', expansionType: 'none', scopeType: 'unknown' }, // 40行離れている ]); - const result = await handleMultipleMatches(error, rl); + const result = await handleMultipleMatches(error, rl, mockConfig); expect(result).not.toBeNull(); // ソート後の配列から選択される(モックのreadlineが選択) @@ -584,7 +594,7 @@ describe('優先順位付けロジック', () => { { start: 14, end: 24, confidence: 'high', expansionType: 'ast', scopeType: 'const' }, ]); - const result = await handleMultipleMatches(error, rl); + const result = await handleMultipleMatches(error, rl, mockConfig); expect(result).not.toBeNull(); // 複数のhigh信頼度マッチがあり、その中でスコープタイプの優先度が高いマッチが自動選択される @@ -604,7 +614,7 @@ describe('優先順位付けロジック', () => { { start: 100, end: 110, confidence: 'high', expansionType: 'ast', scopeType: 'interface' }, // 遠いが高信頼度 ]); - const result = await handleMultipleMatches(error, rl); + const result = await handleMultipleMatches(error, rl, mockConfig); expect(result).not.toBeNull(); // 高信頼度が1つだけなので自動選択される @@ -618,7 +628,7 @@ describe('優先順位付けロジック', () => { mockSearchCodeInFileWithScopeExpansion.mockReturnValue([]); - const result = await handleMultipleMatches(error, rl); + const result = await handleMultipleMatches(error, rl, mockConfig); expect(result).toBeNull(); }); @@ -638,7 +648,7 @@ describe('優先順位付けロジック', () => { }; const rl = createMockReadline(); - const result = await handleMultipleMatches(error, rl); + const result = await handleMultipleMatches(error, rl, mockConfig); expect(result).toBeNull(); expect(mockSearchCodeInFileWithScopeExpansion).not.toHaveBeenCalled(); diff --git a/src/utils/fix.ts b/src/utils/fix.ts index 6566a24..2f0d519 100644 --- a/src/utils/fix.ts +++ b/src/utils/fix.ts @@ -18,6 +18,7 @@ import { } from '@/utils/markdown-edit'; import { askSelectOption } from '@/utils/prompt'; import type { CodeRefError, ExpandedMatch, FixAction } from '@/utils/types'; +import type { CodeRefConfig } from '@/config'; /** * Check if error is fixable @@ -36,7 +37,7 @@ export function isFixableError(error: CodeRefError): boolean { /** * Create fix action for CODE_LOCATION_MISMATCH */ -export function createLocationMismatchFix(error: CodeRefError): FixAction { +export function createLocationMismatchFix(error: CodeRefError, config: CodeRefConfig): FixAction { if (!error.suggestedLines) { throw new Error('CODE_LOCATION_MISMATCH requires suggestedLines'); } @@ -46,7 +47,7 @@ export function createLocationMismatchFix(error: CodeRefError): FixAction { const newComment = ``; // Get actual code for preview - const projectRoot = path.resolve(__dirname, '../../..'); + const projectRoot = config.projectRoot; const absolutePath = path.resolve(projectRoot, ref.refPath); const actualCode = extractLinesFromFile( absolutePath, @@ -130,7 +131,7 @@ function createMoveCommentFix( /** * Create fix action for CODE_BLOCK_MISSING */ -export function createBlockMissingFix(error: CodeRefError): FixAction { +export function createBlockMissingFix(error: CodeRefError, config: CodeRefConfig): FixAction { const { ref } = error; // 0. Early return for whole file reference (no code block needed) @@ -150,7 +151,7 @@ export function createBlockMissingFix(error: CodeRefError): FixAction { } // 2. Code block not found → existing logic (insert) - const projectRoot = path.resolve(__dirname, '../../..'); + const projectRoot = config.projectRoot; const absolutePath = path.resolve(projectRoot, ref.refPath); // Only symbol specified without line numbers @@ -212,9 +213,12 @@ export function createBlockMissingFix(error: CodeRefError): FixAction { /** * Create fix action for CODE_CONTENT_MISMATCH */ -export function createContentMismatchFix(error: CodeRefError): FixAction | FixAction[] { +export function createContentMismatchFix( + error: CodeRefError, + config: CodeRefConfig +): FixAction | FixAction[] { const { ref } = error; - const projectRoot = path.resolve(__dirname, '../../..'); + const projectRoot = config.projectRoot; const absolutePath = path.resolve(projectRoot, ref.refPath); // When only symbol is specified (no line numbers): return multiple options @@ -413,6 +417,7 @@ export async function createMultipleSymbolsFoundFix( */ export async function createFixAction( error: CodeRefError, + config: CodeRefConfig, rl?: readline.Interface ): Promise { if (!isFixableError(error)) { @@ -421,11 +426,11 @@ export async function createFixAction( switch (error.type) { case 'CODE_LOCATION_MISMATCH': - return createLocationMismatchFix(error); + return createLocationMismatchFix(error, config); case 'CODE_BLOCK_MISSING': - return createBlockMissingFix(error); + return createBlockMissingFix(error, config); case 'CODE_CONTENT_MISMATCH': - return createContentMismatchFix(error); + return createContentMismatchFix(error, config); case 'LINE_OUT_OF_RANGE': return createLineOutOfRangeFix(error); case 'SYMBOL_RANGE_MISMATCH': @@ -561,7 +566,8 @@ function prioritizeMatchesWithConfidence( */ export async function handleMultipleMatches( error: CodeRefError, - rl: readline.Interface + rl: readline.Interface, + config: CodeRefConfig ): Promise { const { ref } = error; @@ -569,7 +575,7 @@ export async function handleMultipleMatches( return null; } - const projectRoot = path.resolve(__dirname, '../../..'); + const projectRoot = config.projectRoot; const absolutePath = path.resolve(projectRoot, ref.refPath); // Use AST analysis with scope expansion diff --git a/src/utils/prompt.test.ts b/src/utils/prompt.test.ts index 6273190..aae0a39 100644 --- a/src/utils/prompt.test.ts +++ b/src/utils/prompt.test.ts @@ -210,6 +210,7 @@ describe('prompt', () => { describe('displayFixPreview', () => { let consoleLogSpy: any; + const testProjectRoot = '/tmp/test-project'; beforeEach(() => { consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); @@ -239,7 +240,7 @@ describe('prompt', () => { newEndLine: 25, }; - displayFixPreview(action); + displayFixPreview(action, testProjectRoot); expect(consoleLogSpy).toHaveBeenCalledWith('\nChanges:'); expect(consoleLogSpy).toHaveBeenCalledWith('- Description: Update line numbers'); @@ -264,7 +265,7 @@ describe('prompt', () => { newCodeBlock: 'const x = 1;', }; - displayFixPreview(action); + displayFixPreview(action, testProjectRoot); expect(consoleLogSpy).toHaveBeenCalledWith('\nChanges:'); expect(consoleLogSpy).toHaveBeenCalledWith('- Description: Insert code block'); @@ -292,7 +293,7 @@ describe('prompt', () => { newCodeBlock: 'const y = 2;', }; - displayFixPreview(action); + displayFixPreview(action, testProjectRoot); expect(consoleLogSpy).toHaveBeenCalledWith('\nChanges:'); expect(consoleLogSpy).toHaveBeenCalledWith('- Description: Replace code block'); @@ -316,7 +317,7 @@ describe('prompt', () => { preview: 'Simple preview text', }; - displayFixPreview(action); + displayFixPreview(action, testProjectRoot); expect(consoleLogSpy).toHaveBeenCalledWith('\nChanges:'); expect(consoleLogSpy).toHaveBeenCalledWith('- Description: Update line numbers'); @@ -344,7 +345,7 @@ describe('prompt', () => { newCodeBlock: 'new code', }; - displayFixPreview(action); + displayFixPreview(action, testProjectRoot); expect(consoleLogSpy).toHaveBeenCalledWith('\nChanges:'); expect(consoleLogSpy).toHaveBeenCalledWith('- Description: Replace code block'); @@ -373,7 +374,7 @@ describe('prompt', () => { newCodeBlock: 'const x = 1;\nconst y = 2;\nconst z = 3;', }; - displayFixPreview(action); + displayFixPreview(action, testProjectRoot); expect(consoleLogSpy).toHaveBeenCalledWith('\nChanges:'); expect(consoleLogSpy).toHaveBeenCalledWith('- Description: Update end line number'); diff --git a/src/utils/prompt.ts b/src/utils/prompt.ts index c6cd3f1..325212c 100644 --- a/src/utils/prompt.ts +++ b/src/utils/prompt.ts @@ -78,13 +78,12 @@ export async function askSelectOption( /** * Display fix preview */ -export function displayFixPreview(action: FixAction): void { +export function displayFixPreview(action: FixAction, projectRoot: string): void { console.log('\nChanges:'); console.log(`- Description: ${action.description}`); // Display colored diff based on error type const { error } = action; - const projectRoot = path.resolve(__dirname, '../../..'); const absolutePath = path.resolve(projectRoot, error.ref.refPath); switch (error.type) { From ae5ab6aae477f354c369c1ce442035b7b6ded396 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Wed, 31 Dec 2025 00:36:42 +0900 Subject: [PATCH 32/57] chore: update log format --- src/utils/prompt.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/utils/prompt.ts b/src/utils/prompt.ts index 325212c..c959afd 100644 --- a/src/utils/prompt.ts +++ b/src/utils/prompt.ts @@ -79,8 +79,7 @@ export async function askSelectOption( * Display fix preview */ export function displayFixPreview(action: FixAction, projectRoot: string): void { - console.log('\nChanges:'); - console.log(`- Description: ${action.description}`); + console.log(`\nChanges: ${action.description}`); // Display colored diff based on error type const { error } = action; From aa7ab4895c33c6f406a51efadd6650e1c19404e2 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Wed, 31 Dec 2025 00:51:41 +0900 Subject: [PATCH 33/57] test: fix prompt tests to match updated log format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update test expectations in prompt.test.ts to align with the new log format introduced in ae5ab6a. The displayFixPreview function now outputs "Changes: " on a single line instead of two separate calls. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/utils/prompt.test.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/utils/prompt.test.ts b/src/utils/prompt.test.ts index aae0a39..cbdc324 100644 --- a/src/utils/prompt.test.ts +++ b/src/utils/prompt.test.ts @@ -242,8 +242,7 @@ describe('prompt', () => { displayFixPreview(action, testProjectRoot); - expect(consoleLogSpy).toHaveBeenCalledWith('\nChanges:'); - expect(consoleLogSpy).toHaveBeenCalledWith('- Description: Update line numbers'); + expect(consoleLogSpy).toHaveBeenCalledWith('\nChanges: Update line numbers'); }); it('INSERT_CODE_BLOCKタイプの修正プレビューを表示すること', () => { @@ -267,8 +266,7 @@ describe('prompt', () => { displayFixPreview(action, testProjectRoot); - expect(consoleLogSpy).toHaveBeenCalledWith('\nChanges:'); - expect(consoleLogSpy).toHaveBeenCalledWith('- Description: Insert code block'); + expect(consoleLogSpy).toHaveBeenCalledWith('\nChanges: Insert code block'); expect(consoleLogSpy).toHaveBeenCalledWith('\x1b[32m+ Insert code block:\x1b[0m'); }); @@ -295,8 +293,7 @@ describe('prompt', () => { displayFixPreview(action, testProjectRoot); - expect(consoleLogSpy).toHaveBeenCalledWith('\nChanges:'); - expect(consoleLogSpy).toHaveBeenCalledWith('- Description: Replace code block'); + expect(consoleLogSpy).toHaveBeenCalledWith('\nChanges: Replace code block'); }); it('CODE_LOCATION_MISMATCHタイプの修正プレビューを表示すること(コードブロックなし)', () => { @@ -319,8 +316,7 @@ describe('prompt', () => { displayFixPreview(action, testProjectRoot); - expect(consoleLogSpy).toHaveBeenCalledWith('\nChanges:'); - expect(consoleLogSpy).toHaveBeenCalledWith('- Description: Update line numbers'); + expect(consoleLogSpy).toHaveBeenCalledWith('\nChanges: Update line numbers'); expect(consoleLogSpy).toHaveBeenCalledWith('Simple preview text'); }); @@ -347,8 +343,7 @@ describe('prompt', () => { displayFixPreview(action, testProjectRoot); - expect(consoleLogSpy).toHaveBeenCalledWith('\nChanges:'); - expect(consoleLogSpy).toHaveBeenCalledWith('- Description: Replace code block'); + expect(consoleLogSpy).toHaveBeenCalledWith('\nChanges: Replace code block'); }); it('UPDATE_END_LINEタイプの修正プレビューを表示すること', () => { @@ -376,8 +371,7 @@ describe('prompt', () => { displayFixPreview(action, testProjectRoot); - expect(consoleLogSpy).toHaveBeenCalledWith('\nChanges:'); - expect(consoleLogSpy).toHaveBeenCalledWith('- Description: Update end line number'); + expect(consoleLogSpy).toHaveBeenCalledWith('\nChanges: Update end line number'); }); }); }); From 4de8ca711d4981e15aae5691520ac45c591a8d84 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Wed, 31 Dec 2025 08:48:48 +0900 Subject: [PATCH 34/57] chore: remove unnecessary variable --- src/utils/fix.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/utils/fix.ts b/src/utils/fix.ts index 2f0d519..88ccfc2 100644 --- a/src/utils/fix.ts +++ b/src/utils/fix.ts @@ -151,8 +151,7 @@ export function createBlockMissingFix(error: CodeRefError, config: CodeRefConfig } // 2. Code block not found → existing logic (insert) - const projectRoot = config.projectRoot; - const absolutePath = path.resolve(projectRoot, ref.refPath); + const absolutePath = path.resolve(config.projectRoot, ref.refPath); // Only symbol specified without line numbers if (ref.symbolPath && (ref.startLine === null || ref.endLine === null)) { From 3b3a750b30127e119e6909773fae57c2957e08db Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Wed, 31 Dec 2025 10:32:13 +0900 Subject: [PATCH 35/57] style: update style code format using chalk --- package-lock.json | 7 +- package.json | 1 + src/cli/fix.ts | 11 +- src/utils/styles.test.ts | 333 +++++++++++++++++++++++++++++++++++++++ src/utils/styles.ts | 185 ++++++++++++++++++++++ 5 files changed, 522 insertions(+), 15 deletions(-) create mode 100644 src/utils/styles.test.ts create mode 100644 src/utils/styles.ts diff --git a/package-lock.json b/package-lock.json index 5903b47..6f664bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@typescript-eslint/typescript-estree": "^8.50.1", + "chalk": "^4.1.2", "commander": "^14.0.2" }, "bin": { @@ -2885,7 +2886,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -3197,7 +3197,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -3409,7 +3408,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -3422,7 +3420,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/colorette": { @@ -4257,7 +4254,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6710,7 +6706,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" diff --git a/package.json b/package.json index 16162a8..681541d 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ }, "dependencies": { "@typescript-eslint/typescript-estree": "^8.50.1", + "chalk": "^4.1.2", "commander": "^14.0.2" }, "devDependencies": { diff --git a/src/cli/fix.ts b/src/cli/fix.ts index 2048a48..d1513c4 100644 --- a/src/cli/fix.ts +++ b/src/cli/fix.ts @@ -16,6 +16,7 @@ import * as path from 'path'; import { createBackup } from '@/utils/backup'; import { applyFix, createFixAction, handleMultipleMatches, isFixableError } from '@/utils/fix'; import { askYesNo, createPromptInterface, displayFixPreview } from '@/utils/prompt'; +import { formatFixOptions } from '@/utils/styles'; import type { CodeRefError, FixOptions, FixResult } from '@/utils/types'; import { extractCodeRefs, findMarkdownFiles, validateCodeRef } from '@/core/validate'; import { loadFixConfig, getDocsPath, type CodeRefFixConfig } from '@/config'; @@ -141,15 +142,7 @@ export async function main(): Promise { // If there are multiple options, let the user choose if (Array.isArray(fixActionResult)) { console.log('\n🛠️ Please select a fix method:\n'); - - fixActionResult.forEach((opt, index) => { - console.log(` ${index + 1}. ${opt.description}`); - const previewLines = opt.preview.split('\n'); - previewLines.forEach((line) => { - console.log(` ${line}`); - }); - console.log(''); - }); + console.log(formatFixOptions(fixActionResult)); if (options.auto) { // Auto-select first option in auto mode diff --git a/src/utils/styles.test.ts b/src/utils/styles.test.ts new file mode 100644 index 0000000..854dadf --- /dev/null +++ b/src/utils/styles.test.ts @@ -0,0 +1,333 @@ +/** + * Tests for styling utilities + */ + +import { formatFixOptions } from './styles'; +import type { FixAction, CodeRefError } from './types'; + +describe('styles', () => { + // Save original environment variable + let originalNoColor: string | undefined; + + beforeEach(() => { + originalNoColor = process.env.NO_COLOR; + }); + + afterEach(() => { + // Restore original environment variable + if (originalNoColor === undefined) { + delete process.env.NO_COLOR; + } else { + process.env.NO_COLOR = originalNoColor; + } + }); + + describe('formatFixOptions', () => { + it('should format multiple options correctly', () => { + const mockError: CodeRefError = { + type: 'CODE_CONTENT_MISMATCH', + message: 'Code content mismatch', + ref: { + fullMatch: '', + refPath: 'test.ts', + startLine: 10, + endLine: 20, + docFile: 'test.md', + }, + }; + + const options: FixAction[] = [ + { + type: 'REPLACE_CODE_BLOCK', + error: mockError, + description: 'Replace code block with entire symbol', + preview: '```typescript\nfunction foo() {\n return 42;\n}\n```', + }, + { + type: 'UPDATE_LINE_NUMBERS', + error: mockError, + description: 'Update line numbers only', + preview: 'Lines: 10-25', + }, + ]; + + const result = formatFixOptions(options); + + // Should contain option headers + expect(result).toContain('Option 1'); + expect(result).toContain('Option 2'); + + // Should contain descriptions + expect(result).toContain('Replace code block with entire symbol'); + expect(result).toContain('Update line numbers only'); + + // Should contain box drawing characters + expect(result).toContain('┌'); + expect(result).toContain('└'); + expect(result).toContain('│'); + }); + + it('should display short preview as is', () => { + const mockError: CodeRefError = { + type: 'CODE_CONTENT_MISMATCH', + message: 'Code content mismatch', + ref: { + fullMatch: '', + refPath: 'test.ts', + startLine: 10, + endLine: 15, + docFile: 'test.md', + }, + }; + + const options: FixAction[] = [ + { + type: 'REPLACE_CODE_BLOCK', + error: mockError, + description: 'Replace with short code', + preview: '```typescript\nfunction foo() {\n return 42;\n}\n```', + }, + ]; + + const result = formatFixOptions(options); + + // Should contain all lines of the preview + expect(result).toContain('function foo()'); + expect(result).toContain('return 42'); + + // Should NOT contain truncation message + expect(result).not.toContain('more lines'); + }); + + it('should truncate long preview (>20 lines)', () => { + const mockError: CodeRefError = { + type: 'CODE_CONTENT_MISMATCH', + message: 'Code content mismatch', + ref: { + fullMatch: '', + refPath: 'test.ts', + startLine: 1, + endLine: 30, + docFile: 'test.md', + }, + }; + + // Create a preview with 25 lines + const longPreview = Array.from({ length: 25 }, (_, i) => `line ${i + 1}`).join('\n'); + + const options: FixAction[] = [ + { + type: 'REPLACE_CODE_BLOCK', + error: mockError, + description: 'Replace with long code', + preview: longPreview, + }, + ]; + + const result = formatFixOptions(options); + + // Should contain first few lines + expect(result).toContain('line 1'); + expect(result).toContain('line 15'); + + // Should contain truncation message + expect(result).toContain('10 more lines'); + + // Should NOT contain all lines + expect(result).not.toContain('line 25'); + }); + + it('should handle special characters properly', () => { + const mockError: CodeRefError = { + type: 'CODE_CONTENT_MISMATCH', + message: 'Code content mismatch', + ref: { + fullMatch: '', + refPath: 'test.ts', + startLine: 10, + endLine: 15, + docFile: 'test.md', + }, + }; + + const options: FixAction[] = [ + { + type: 'REPLACE_CODE_BLOCK', + error: mockError, + description: 'Replace with special chars', + preview: '```typescript\nconst str = "Hello ";\nconst regex = /\\d+/;\n```', + }, + ]; + + const result = formatFixOptions(options); + + // Should contain special characters + expect(result).toContain('Hello '); + expect(result).toContain('/\\d+/'); + }); + + it('should extract and display line information for range', () => { + const mockError: CodeRefError = { + type: 'UPDATE_LINE_NUMBERS', + message: 'Line numbers need update', + ref: { + fullMatch: '', + refPath: 'test.ts', + startLine: 10, + endLine: 25, + docFile: 'test.md', + }, + }; + + const options: FixAction[] = [ + { + type: 'UPDATE_LINE_NUMBERS', + error: mockError, + description: 'Update line numbers', + preview: 'Lines: 10-25\nSome code here', + }, + ]; + + const result = formatFixOptions(options); + + // Should contain formatted line information + expect(result).toContain('Lines: 10-25 (16 lines)'); + }); + + it('should extract and display line information for single line', () => { + const mockError: CodeRefError = { + type: 'UPDATE_LINE_NUMBERS', + message: 'Line numbers need update', + ref: { + fullMatch: '', + refPath: 'test.ts', + startLine: 10, + endLine: 10, + docFile: 'test.md', + }, + }; + + const options: FixAction[] = [ + { + type: 'UPDATE_LINE_NUMBERS', + error: mockError, + description: 'Update line number', + preview: 'Line: 10\nSome code here', + }, + ]; + + const result = formatFixOptions(options); + + // Should contain formatted line information for single line + expect(result).toContain('Line: 10'); + }); + }); + + describe('color disabled environment', () => { + it('should not output color codes when NO_COLOR=1', () => { + // Set NO_COLOR environment variable + process.env.NO_COLOR = '1'; + + // Force re-import to pick up environment variable + // Note: This is tricky in Node.js, so we'll just check that chalk respects NO_COLOR + const mockError: CodeRefError = { + type: 'CODE_CONTENT_MISMATCH', + message: 'Code content mismatch', + ref: { + fullMatch: '', + refPath: 'test.ts', + startLine: 10, + endLine: 15, + docFile: 'test.md', + }, + }; + + const options: FixAction[] = [ + { + type: 'REPLACE_CODE_BLOCK', + error: mockError, + description: 'Test option', + preview: 'function foo() {}', + }, + ]; + + const result = formatFixOptions(options); + + // chalk should automatically detect NO_COLOR and disable colors + // We just verify the function doesn't crash and returns text + expect(result).toContain('Option 1'); + expect(result).toContain('Test option'); + }); + }); + + describe('box drawing', () => { + it('should contain box drawing characters', () => { + const mockError: CodeRefError = { + type: 'CODE_CONTENT_MISMATCH', + message: 'Code content mismatch', + ref: { + fullMatch: '', + refPath: 'test.ts', + startLine: 10, + endLine: 15, + docFile: 'test.md', + }, + }; + + const options: FixAction[] = [ + { + type: 'REPLACE_CODE_BLOCK', + error: mockError, + description: 'Test option', + preview: 'Some code', + }, + ]; + + const result = formatFixOptions(options); + + // Should contain box drawing characters + expect(result).toContain('┌'); + expect(result).toContain('─'); + expect(result).toContain('└'); + expect(result).toContain('│'); + }); + + it('should visually separate each option', () => { + const mockError: CodeRefError = { + type: 'CODE_CONTENT_MISMATCH', + message: 'Code content mismatch', + ref: { + fullMatch: '', + refPath: 'test.ts', + startLine: 10, + endLine: 15, + docFile: 'test.md', + }, + }; + + const options: FixAction[] = [ + { + type: 'REPLACE_CODE_BLOCK', + error: mockError, + description: 'First option', + preview: 'Code 1', + }, + { + type: 'UPDATE_LINE_NUMBERS', + error: mockError, + description: 'Second option', + preview: 'Code 2', + }, + ]; + + const result = formatFixOptions(options); + + // Each option should have its own box + const topLeftCount = (result.match(/┌/g) || []).length; + const bottomLeftCount = (result.match(/└/g) || []).length; + + expect(topLeftCount).toBe(2); // Two options = two top-left corners + expect(bottomLeftCount).toBe(2); // Two options = two bottom-left corners + }); + }); +}); diff --git a/src/utils/styles.ts b/src/utils/styles.ts new file mode 100644 index 0000000..faad7f4 --- /dev/null +++ b/src/utils/styles.ts @@ -0,0 +1,185 @@ +/** + * Styling utilities for CLI output using chalk + */ + +import chalk from 'chalk'; +import type { FixAction } from './types'; + +/** + * Color scheme (consistent with diff-display.ts and new additions) + */ +const colors = { + // Existing colors from diff-display.ts + success: chalk.green, + error: chalk.red, + dim: chalk.dim, + + // New colors for fix options + primary: chalk.cyan.bold, // Option numbers + muted: chalk.gray, // Supplementary info + code: chalk.yellow, // Code blocks +}; + +/** + * Box drawing characters + */ +const box = { + topLeft: '┌', + topRight: '┐', + bottomLeft: '└', + bottomRight: '┘', + horizontal: '─', + vertical: '│', +}; + +/** + * Maximum number of lines to show in preview + */ +const MAX_PREVIEW_LINES = 20; + +/** + * Number of lines to show when truncating + */ +const TRUNCATE_SHOW_LINES = 15; + +/** + * Format fix options for display + * @param options Array of fix actions to display + * @returns Formatted string with styled options + */ +export function formatFixOptions(options: FixAction[]): string { + const output: string[] = []; + + options.forEach((option, index) => { + const optionNumber = index + 1; + + // Option header with box drawing + const headerText = ` Option ${optionNumber} `; + const headerLine = box.horizontal.repeat(60 - headerText.length); + output.push(colors.dim(` ${box.topLeft}${box.horizontal}${headerText}${headerLine}`)); + + // Option description + output.push(colors.dim(` ${box.vertical} `) + colors.primary(option.description)); + + // Extract line information from preview if available + const lineInfo = extractLineInfo(option.preview); + if (lineInfo) { + output.push(colors.dim(` ${box.vertical} `) + colors.muted(lineInfo)); + } + + // Preview section + if (option.preview) { + output.push(colors.dim(` ${box.vertical}`)); + output.push(colors.dim(` ${box.vertical} `) + colors.muted('Preview:')); + + const previewLines = formatPreview(option.preview); + previewLines.forEach((line) => { + output.push(colors.dim(` ${box.vertical} `) + line); + }); + } + + // Bottom border + output.push(colors.dim(` ${box.bottomLeft}${box.horizontal.repeat(60)}`)); + + // Add spacing between options (except after the last one) + if (index < options.length - 1) { + output.push(''); + } + }); + + return output.join('\n'); +} + +/** + * Extract line information from preview text + * @param preview Preview text + * @returns Line information string or null + */ +function extractLineInfo(preview: string): string | null { + // Look for patterns like "Lines: 10-25" or "Line: 10" + const regex = /Lines?: (\d+)-?(\d+)?/i; + const linesMatch = regex.exec(preview); + if (linesMatch) { + const start = linesMatch[1]; + const end = linesMatch[2]; + if (end) { + const lineCount = parseInt(end) - parseInt(start) + 1; + return `Lines: ${start}-${end} (${lineCount} lines)`; + } else { + return `Line: ${start}`; + } + } + return null; +} + +/** + * Format preview text with truncation and syntax highlighting + * @param preview Preview text + * @returns Array of formatted lines + */ +function formatPreview(preview: string): string[] { + const lines = preview.split('\n'); + const output: string[] = []; + + // Truncate if too long + if (lines.length > MAX_PREVIEW_LINES) { + const truncatedLines = lines.slice(0, TRUNCATE_SHOW_LINES); + const remainingCount = lines.length - TRUNCATE_SHOW_LINES; + + truncatedLines.forEach((line) => { + output.push(formatCodeLine(line)); + }); + + output.push(colors.muted(`... (${remainingCount} more lines)`)); + } else { + lines.forEach((line) => { + output.push(formatCodeLine(line)); + }); + } + + return output; +} + +/** + * Format a single code line with basic syntax highlighting + * @param line Code line + * @returns Formatted line + */ +function formatCodeLine(line: string): string { + // Apply basic syntax highlighting + // Detect code block markers + if (line.trim().startsWith('```')) { + return colors.dim(line); + } + + // Detect keywords (basic highlighting) + const keywords = [ + 'function', + 'const', + 'let', + 'var', + 'class', + 'interface', + 'type', + 'export', + 'import', + 'return', + 'if', + 'else', + 'for', + 'while', + ]; + + let formattedLine = line; + keywords.forEach((keyword) => { + const regex = new RegExp(`\\b(${keyword})\\b`, 'g'); + formattedLine = formattedLine.replace(regex, chalk.cyan('$1')); + }); + + // Highlight strings (simple patterns for single and double quotes) + formattedLine = formattedLine.replace(/'[^']*'/g, (match) => colors.code(match)); + formattedLine = formattedLine.replace(/"[^"]*"/g, (match) => colors.code(match)); + formattedLine = formattedLine.replace(/`[^`]*`/g, (match) => colors.code(match)); + + return formattedLine; +} From 418c4aa9b19ed559895be93ce804975bc8b4498b Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Wed, 31 Dec 2025 10:44:16 +0900 Subject: [PATCH 36/57] test: add unit test for utils --- src/utils/diff-display.test.ts | 29 ++++++++ src/utils/styles.test.ts | 130 +++++++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+) diff --git a/src/utils/diff-display.test.ts b/src/utils/diff-display.test.ts index dd84a17..e18c871 100644 --- a/src/utils/diff-display.test.ts +++ b/src/utils/diff-display.test.ts @@ -45,6 +45,35 @@ describe('displayCodeDiff', () => { expect(result).toContain('+ new line'); }); + + it('actual が expected より短い場合も正しく表示する', () => { + const expected = 'line1\nline2\nline3'; + const actual = 'line1'; + const result = displayCodeDiff(expected, actual); + + // 一致する行 + expect(result).toContain(' line1'); + + // 削除された行(expected にのみ存在) + expect(result).toContain('- line2'); + expect(result).toContain('- line3'); + }); + + it('expected と actual の長さが異なり、途中で一致しない場合', () => { + const expected = 'line1\nline2'; + const actual = 'line1\nmodified\nline3'; + const result = displayCodeDiff(expected, actual); + + // 一致する行 + expect(result).toContain(' line1'); + + // 変更された行 + expect(result).toContain('- line2'); + expect(result).toContain('+ modified'); + + // 追加された行 + expect(result).toContain('+ line3'); + }); }); describe('displayLineRangeDiff', () => { diff --git a/src/utils/styles.test.ts b/src/utils/styles.test.ts index 854dadf..bd6ca7d 100644 --- a/src/utils/styles.test.ts +++ b/src/utils/styles.test.ts @@ -221,6 +221,71 @@ describe('styles', () => { // Should contain formatted line information for single line expect(result).toContain('Line: 10'); }); + + it('should handle empty preview', () => { + const mockError: CodeRefError = { + type: 'UPDATE_LINE_NUMBERS', + message: 'Line numbers need update', + ref: { + fullMatch: '', + refPath: 'test.ts', + startLine: 10, + endLine: 15, + docFile: 'test.md', + }, + }; + + const options: FixAction[] = [ + { + type: 'UPDATE_LINE_NUMBERS', + error: mockError, + description: 'Update line numbers', + preview: '', + }, + ]; + + const result = formatFixOptions(options); + + // Should contain option description + expect(result).toContain('Update line numbers'); + // Should contain box drawing + expect(result).toContain('┌'); + // Should NOT contain Preview section + expect(result).not.toContain('Preview:'); + }); + + it('should handle preview without line information', () => { + const mockError: CodeRefError = { + type: 'REPLACE_CODE_BLOCK', + message: 'Code content mismatch', + ref: { + fullMatch: '', + refPath: 'test.ts', + startLine: 10, + endLine: 15, + docFile: 'test.md', + }, + }; + + const options: FixAction[] = [ + { + type: 'REPLACE_CODE_BLOCK', + error: mockError, + description: 'Replace code block', + preview: '```typescript\nfunction test() {}\n```', + }, + ]; + + const result = formatFixOptions(options); + + // Should contain option description + expect(result).toContain('Replace code block'); + // Should contain preview + expect(result).toContain('Preview:'); + expect(result).toContain('function test()'); + // Should NOT contain line info (no "Lines:" or "Line:" in preview) + expect(result).not.toMatch(/Lines?: \d+/); + }); }); describe('color disabled environment', () => { @@ -260,6 +325,71 @@ describe('styles', () => { }); }); + describe('syntax highlighting', () => { + it('should highlight code block markers as dim', () => { + const mockError: CodeRefError = { + type: 'REPLACE_CODE_BLOCK', + message: 'Code content mismatch', + ref: { + fullMatch: '', + refPath: 'test.ts', + startLine: 10, + endLine: 15, + docFile: 'test.md', + }, + }; + + const options: FixAction[] = [ + { + type: 'REPLACE_CODE_BLOCK', + error: mockError, + description: 'Replace code', + preview: '```typescript\ncode here\n```', + }, + ]; + + const result = formatFixOptions(options); + + // Should contain code block markers + expect(result).toContain('```typescript'); + expect(result).toContain('```'); + }); + + it('should highlight keywords in code', () => { + const mockError: CodeRefError = { + type: 'REPLACE_CODE_BLOCK', + message: 'Code content mismatch', + ref: { + fullMatch: '', + refPath: 'test.ts', + startLine: 10, + endLine: 15, + docFile: 'test.md', + }, + }; + + const options: FixAction[] = [ + { + type: 'REPLACE_CODE_BLOCK', + error: mockError, + description: 'Replace code', + preview: + 'function test() {\n const x = 42;\n return x;\n}\nexport class Foo {}\ninterface Bar {}', + }, + ]; + + const result = formatFixOptions(options); + + // Should contain code with keywords + expect(result).toContain('function'); + expect(result).toContain('const'); + expect(result).toContain('return'); + expect(result).toContain('export'); + expect(result).toContain('class'); + expect(result).toContain('interface'); + }); + }); + describe('box drawing', () => { it('should contain box drawing characters', () => { const mockError: CodeRefError = { From e1364268484649cfbe47cfa8a0142234e5414056 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Wed, 31 Dec 2025 10:58:06 +0900 Subject: [PATCH 37/57] test: fix test errors by chalk --- jest.config.js | 1 + jest.setup.js | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 jest.setup.js diff --git a/jest.config.js b/jest.config.js index b14efe5..f82910d 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,6 +4,7 @@ module.exports = { testEnvironment: 'node', roots: ['/src'], testMatch: ['**/*.test.ts'], + setupFilesAfterEnv: ['/jest.setup.js'], collectCoverageFrom: [ 'src/**/*.ts', '!src/**/*.d.ts', diff --git a/jest.setup.js b/jest.setup.js new file mode 100644 index 0000000..b839eea --- /dev/null +++ b/jest.setup.js @@ -0,0 +1,11 @@ +/** + * Jest setup file to configure test environment + */ + +// Disable chalk colors in tests to make string matching easier +process.env.NO_COLOR = '1'; +process.env.FORCE_COLOR = '0'; + +// Force chalk to disable colors by setting level to 0 +const chalk = require('chalk'); +chalk.level = 0; From 41c5fae7d09d4746c9fbc2aefb7beef36af138d7 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Wed, 31 Dec 2025 11:12:27 +0900 Subject: [PATCH 38/57] docs: add documantation.md --- docs/README.md | 1 + docs/development/documentation.md | 143 ++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 docs/development/documentation.md diff --git a/docs/README.md b/docs/README.md index 1137d0b..79b5b85 100644 --- a/docs/README.md +++ b/docs/README.md @@ -19,6 +19,7 @@ Start here if you want to contribute: - [Coding Standards](development/coding-standards.md) - Style guide and conventions - [Git Conventions](development/git-conventions.md) - Commit message format - [Testing Guide](development/testing-guide.md) - Writing and running tests +- [Documentation Guidelines](development/documentation.md) - Writing and maintaining docs ## Architecture & Design diff --git a/docs/development/documentation.md b/docs/development/documentation.md new file mode 100644 index 0000000..1dc7f99 --- /dev/null +++ b/docs/development/documentation.md @@ -0,0 +1,143 @@ +# Documentation Guidelines + +This guide explains how to write and maintain documentation for the @cawpea/coderef project. + +## Documentation Structure + +All documentation lives in the `docs/` directory and follows a three-tier structure: + +``` +docs/ +├── README.md # Documentation index and navigation +├── user-guide/ # End-user documentation +├── development/ # Contributor guides +└── architecture/ # Technical deep-dives +``` + +## Markdown Style Guide + +### Headers + +Use ATX-style headers (`#`) with a space after the hash: + +```markdown +# H1 - Document Title + +## H2 - Major Section + +### H3 - Subsection +``` + +- Each document should have exactly one H1 at the top +- Use H2 for major sections, H3 for subsections +- Avoid skipping header levels (don't jump from H2 to H4) + +### Code Blocks + +Always specify the language for syntax highlighting: + +````markdown +```bash +npm run build +``` + +```typescript +function example(): void { + console.log('Hello'); +} +``` +```` + +For command-line examples, use: + +- `bash` for shell commands +- `typescript` for TypeScript code +- `javascript` for JavaScript code +- `json` for JSON configuration +- `markdown` for markdown examples + +### Lists + +Use consistent formatting: + +```markdown +- Unordered lists use hyphens (-) +- Not asterisks (\*) or plus (+) +- Keep items concise + +1. Ordered lists use numbers +2. Start from 1 and increment +3. Use for sequential steps +``` + +### Links + +#### Internal Links + +Use relative paths from the current file: + +```markdown +[Coding Standards](coding-standards.md) +[Architecture Overview](../architecture/overview.md) +[Main README](../../README.md) +``` + +#### External Links + +Provide descriptive link text: + +```markdown +Good: [Conventional Commits specification](https://www.conventionalcommits.org/) +Bad: Click [here](https://www.conventionalcommits.org/) +``` + +### Emphasis + +- **Bold** for UI elements, file names, important terms: `**package.json**` +- _Italic_ for emphasis or introducing new terms: `*CODE_REF comments*` +- `Code` for inline code, commands, values: `` `npm install` `` + +## Using CODE_REF in Documentation + +Since this project validates CODE_REF comments in markdown, follow these practices: + +### Example Code with CODE_REF + +When showing code examples that reference actual source code, use CODE_REF comments: + +````markdown +```typescript +// CODE_REF: src/utils/parser.ts#parseCodeRef +export function parseCodeRef(comment: string): CodeRef { + // Implementation +} +``` +```` + +### Explaining CODE_REF Syntax + +When documenting CODE_REF syntax itself, use markdown code blocks: + +````markdown +The basic syntax is: + +``` +// CODE_REF: # +``` +```` + +### Best Practices for CODE_REF + +- Use CODE_REF when showing actual implementation code +- Keep referenced code snippets short and focused +- Update documentation when refactoring referenced code + +## Common Mistakes to Avoid + +- Don't use `
` tags (use blank lines) +- Don't mix list markers (stick to `-` for unordered lists) +- Don't forget language specifiers in code blocks +- Don't use relative links that go outside the repository +- Don't commit documentation without testing examples +- Don't use inline HTML when markdown suffices +- Don't forget to update `docs/README.md` when adding new documents From 26e561b5b62c6af6e05f0cb270bae9e23e2db745 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Wed, 31 Dec 2025 16:56:50 +0900 Subject: [PATCH 39/57] build: add scripts to check diff to update documents --- .github/workflows/docs-validation.yml | 31 ++++++ package-lock.json | 44 ++++++++ package.json | 2 + scripts/lib/git-diff.ts | 138 ++++++++++++++++++++++++++ scripts/lib/path-checker.ts | 121 ++++++++++++++++++++++ scripts/lib/types.ts | 33 ++++++ scripts/validate-docs.ts | 128 ++++++++++++++++++++++++ 7 files changed, 497 insertions(+) create mode 100644 .github/workflows/docs-validation.yml create mode 100644 scripts/lib/git-diff.ts create mode 100644 scripts/lib/path-checker.ts create mode 100644 scripts/lib/types.ts create mode 100644 scripts/validate-docs.ts diff --git a/.github/workflows/docs-validation.yml b/.github/workflows/docs-validation.yml new file mode 100644 index 0000000..6506172 --- /dev/null +++ b/.github/workflows/docs-validation.yml @@ -0,0 +1,31 @@ +name: Documentation Validation + +on: + pull_request: + branches: [main, develop] + +jobs: + validate-docs: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [22.x] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch full history for git diff + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Validate documentation updates + run: npm run docs:validate diff --git a/package-lock.json b/package-lock.json index 6f664bd..5ca8775 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "prettier": "^3.4.2", "ts-jest": "^29.4.5", "tsup": "^8.0.0", + "tsx": "^4.19.2", "typescript": "^5.9.3", "typescript-eslint": "^8.50.1" }, @@ -4174,6 +4175,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", @@ -6248,6 +6262,16 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/restore-cursor": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", @@ -7062,6 +7086,26 @@ "node": ">= 12" } }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index 681541d..e33da76 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "format": "prettier --write \"src/**/*.ts\" \"*.{js,json,md}\"", "format:check": "prettier --check \"src/**/*.ts\" \"*.{js,json,md}\"", "type-check": "tsc --noEmit", + "docs:validate": "tsx scripts/validate-docs.ts", "prepublishOnly": "npm run build && npm test" }, "engines": { @@ -68,6 +69,7 @@ "prettier": "^3.4.2", "ts-jest": "^29.4.5", "tsup": "^8.0.0", + "tsx": "^4.19.2", "typescript": "^5.9.3", "typescript-eslint": "^8.50.1" }, diff --git a/scripts/lib/git-diff.ts b/scripts/lib/git-diff.ts new file mode 100644 index 0000000..6ba170f --- /dev/null +++ b/scripts/lib/git-diff.ts @@ -0,0 +1,138 @@ +import { execSync } from 'child_process'; + +import type { GitDiff, GitFileChange } from './types'; + +/** + * Execute a git command + */ +export function execGit(args: string[]): string { + try { + const result = execSync(`git ${args.join(' ')}`, { + encoding: 'utf-8', + stdio: ['pipe', 'pipe', 'pipe'], + }); + return result.trim(); + } catch (error: any) { + throw new Error(`Git command failed: ${error.message}`); + } +} + +/** + * Check if a branch exists + */ +function branchExists(branchName: string): boolean { + try { + execGit(['rev-parse', '--verify', branchName]); + return true; + } catch { + return false; + } +} + +/** + * Get current branch name + */ +function getCurrentBranch(): string { + try { + return execGit(['rev-parse', '--abbrev-ref', 'HEAD']); + } catch { + throw new Error('Failed to get current branch'); + } +} + +/** + * Count commits between two refs + */ +function countCommits(from: string, to: string): number { + try { + const result = execGit(['rev-list', '--count', `${from}..${to}`]); + return parseInt(result, 10); + } catch { + return Infinity; + } +} + +/** + * Detect base branch (develop or main) based on merge-base + */ +export function detectBaseBranch(): string { + const currentBranch = getCurrentBranch(); + + // Skip validation if we're on main or develop + if (currentBranch === 'main' || currentBranch === 'develop') { + throw new Error( + `Cannot validate from ${currentBranch} branch. Please run this from a feature branch.` + ); + } + + // Check which branches exist + const mainExists = branchExists('main'); + const developExists = branchExists('develop'); + + if (!mainExists && !developExists) { + throw new Error('Neither main nor develop branch exists'); + } + + // If only one exists, use that + if (!mainExists) return 'develop'; + if (!developExists) return 'main'; + + // Both exist - find merge base for each and count commits + try { + const mainMergeBase = execGit(['merge-base', 'HEAD', 'main']); + const developMergeBase = execGit(['merge-base', 'HEAD', 'develop']); + + const commitsFromMain = countCommits(mainMergeBase, 'HEAD'); + const commitsFromDevelop = countCommits(developMergeBase, 'HEAD'); + + // Return the branch with fewer commits (more recent fork point) + return commitsFromDevelop <= commitsFromMain ? 'develop' : 'main'; + } catch (error: any) { + // Fallback to develop if merge-base fails + return 'develop'; + } +} + +/** + * Get changed files between base branch and HEAD + */ +export function getChangedFiles(baseBranch?: string): GitDiff { + const base = baseBranch || detectBaseBranch(); + + try { + // Get diff between base branch and HEAD + const diffOutput = execGit(['diff', '--name-status', `${base}...HEAD`]); + + if (!diffOutput) { + return { + files: [], + stats: { added: 0, modified: 0, deleted: 0 }, + }; + } + + const files: GitFileChange[] = []; + const stats = { added: 0, modified: 0, deleted: 0 }; + + // Parse diff output + const lines = diffOutput.split('\n').filter((line) => line.trim()); + + for (const line of lines) { + const [status, ...pathParts] = line.split('\t'); + const path = pathParts.join('\t'); // Handle paths with tabs + + if (!status || !path) continue; + + const fileStatus = status.charAt(0) as 'A' | 'M' | 'D' | 'R'; + files.push({ path, status: fileStatus }); + + // Update stats + if (fileStatus === 'A') stats.added++; + else if (fileStatus === 'M') stats.modified++; + else if (fileStatus === 'D') stats.deleted++; + } + + return { files, stats }; + } catch (error: any) { + throw new Error(`Failed to get changed files: ${error.message}`); + } +} diff --git a/scripts/lib/path-checker.ts b/scripts/lib/path-checker.ts new file mode 100644 index 0000000..ad5bf4c --- /dev/null +++ b/scripts/lib/path-checker.ts @@ -0,0 +1,121 @@ +import type { RequirementResult } from './types'; + +/** + * Paths that require documentation updates + */ +const DOCS_REQUIRED_PATHS = ['src/cli/', 'src/index.ts', 'bin/', 'src/core/']; + +/** + * Documentation directory + */ +const DOCS_DIR = 'docs/'; + +/** + * Suggestions for documentation updates based on changed paths + */ +const DOCS_SUGGESTIONS: Record = { + 'src/cli/': ['docs/user-guide/cli-usage.md'], + 'src/index.ts': ['docs/user-guide/', 'docs/architecture/overview.md'], + 'bin/': ['docs/user-guide/installation.md'], + 'src/core/': ['docs/architecture/overview.md'], +}; + +/** + * Check if a path requires documentation update + */ +function pathRequiresDocs(filePath: string): boolean { + return DOCS_REQUIRED_PATHS.some((requiredPath) => { + if (requiredPath.endsWith('/')) { + return filePath.startsWith(requiredPath); + } + return filePath === requiredPath; + }); +} + +/** + * Check if a path is in the docs directory + */ +function isDocsPath(filePath: string): boolean { + return filePath.startsWith(DOCS_DIR); +} + +/** + * Check if changed files require documentation updates + */ +export function requiresDocsUpdate(changedFiles: string[]): RequirementResult { + const affectedPaths: string[] = []; + const suggestions: string[] = []; + + // Check each changed file + for (const file of changedFiles) { + if (pathRequiresDocs(file)) { + // Find matching required path + for (const requiredPath of DOCS_REQUIRED_PATHS) { + const matches = requiredPath.endsWith('/') + ? file.startsWith(requiredPath) + : file === requiredPath; + + if (matches && !affectedPaths.includes(requiredPath)) { + affectedPaths.push(requiredPath); + + // Add suggestions + const pathSuggestions = DOCS_SUGGESTIONS[requiredPath] || []; + for (const suggestion of pathSuggestions) { + if (!suggestions.includes(suggestion)) { + suggestions.push(suggestion); + } + } + } + } + } + } + + if (affectedPaths.length === 0) { + return { + required: false, + reason: 'No user-facing code changes detected', + affectedPaths: [], + suggestions: [], + }; + } + + return { + required: true, + reason: `Changes detected in: ${affectedPaths.join(', ')}`, + affectedPaths, + suggestions, + }; +} + +/** + * Check if docs directory has changes + */ +export function hasDocsChanges(changedFiles: string[]): boolean { + return changedFiles.some((file) => isDocsPath(file)); +} + +/** + * Get list of documentation files that should be updated + */ +export function suggestDocsToUpdate(changedFiles: string[]): string[] { + const suggestions: string[] = []; + + for (const file of changedFiles) { + for (const requiredPath of DOCS_REQUIRED_PATHS) { + const matches = requiredPath.endsWith('/') + ? file.startsWith(requiredPath) + : file === requiredPath; + + if (matches) { + const pathSuggestions = DOCS_SUGGESTIONS[requiredPath] || []; + for (const suggestion of pathSuggestions) { + if (!suggestions.includes(suggestion)) { + suggestions.push(suggestion); + } + } + } + } + } + + return suggestions; +} diff --git a/scripts/lib/types.ts b/scripts/lib/types.ts new file mode 100644 index 0000000..b72326a --- /dev/null +++ b/scripts/lib/types.ts @@ -0,0 +1,33 @@ +/** + * Type definitions for documentation validation + */ + +/** + * Git file change information + */ +export interface GitFileChange { + path: string; + status: 'A' | 'M' | 'D' | 'R'; +} + +/** + * Git diff result + */ +export interface GitDiff { + files: GitFileChange[]; + stats: { + added: number; + modified: number; + deleted: number; + }; +} + +/** + * Documentation requirement result + */ +export interface RequirementResult { + required: boolean; + reason: string; + affectedPaths: string[]; + suggestions: string[]; +} diff --git a/scripts/validate-docs.ts b/scripts/validate-docs.ts new file mode 100644 index 0000000..7d8b330 --- /dev/null +++ b/scripts/validate-docs.ts @@ -0,0 +1,128 @@ +#!/usr/bin/env node + +/** + * Documentation validation script + * + * Validates that documentation is updated when user-facing code changes. + */ + +import chalk from 'chalk'; + +import { detectBaseBranch, getChangedFiles } from './lib/git-diff'; +import { requiresDocsUpdate, hasDocsChanges } from './lib/path-checker'; + +/** + * Main validation function + */ +async function main(): Promise { + console.log('🔍 Validating documentation updates...\n'); + + try { + // Detect base branch + let baseBranch: string; + try { + baseBranch = detectBaseBranch(); + console.log(`Base branch detected: ${chalk.cyan(baseBranch)}`); + } catch (error: any) { + console.log(chalk.yellow(`⚠️ ${error.message}`)); + console.log('\nValidation skipped.\n'); + process.exit(0); + } + + // Get changed files + const diff = getChangedFiles(baseBranch); + console.log(`Comparing: ${chalk.cyan(`${baseBranch}...HEAD`)}\n`); + + // Check if there are any changes + if (diff.files.length === 0) { + console.log(chalk.yellow('⚠️ No changes detected\n')); + console.log('Validation skipped.\n'); + process.exit(0); + } + + // Get file paths + const changedPaths = diff.files.map((f) => f.path); + + // Check if documentation update is required + const requirement = requiresDocsUpdate(changedPaths); + + if (!requirement.required) { + // No documentation update required + console.log('Changes detected:'); + for (const file of diff.files) { + const statusIcon = file.status === 'A' ? '+' : file.status === 'M' ? '~' : '-'; + const statusColor = + file.status === 'A' ? chalk.green : file.status === 'M' ? chalk.yellow : chalk.red; + console.log(` ${statusColor(statusIcon)} ${file.path}`); + } + console.log(''); + console.log(chalk.yellow('⚠️ Documentation update not required for these changes')); + console.log('\nValidation skipped.\n'); + process.exit(0); + } + + // Documentation update is required - check if docs have changes + const docsUpdated = hasDocsChanges(changedPaths); + + // Display changed files + console.log('Changes detected in user-facing code:'); + for (const file of diff.files) { + if (requirement.affectedPaths.some((path) => file.path.startsWith(path))) { + const statusIcon = file.status === 'A' ? '+' : file.status === 'M' ? '~' : '-'; + const statusColor = + file.status === 'A' ? chalk.green : file.status === 'M' ? chalk.yellow : chalk.red; + console.log(` ${statusColor(statusIcon)} ${file.path}`); + } + } + console.log(''); + + if (docsUpdated) { + // Success: docs have been updated + console.log(chalk.green('✅ Documentation validation passed')); + console.log(''); + console.log('Documentation updated:'); + for (const file of diff.files) { + if (file.path.startsWith('docs/')) { + const statusIcon = file.status === 'A' ? '+' : file.status === 'M' ? '~' : '-'; + const statusColor = + file.status === 'A' ? chalk.green : file.status === 'M' ? chalk.yellow : chalk.red; + console.log(` ${statusColor(statusIcon)} ${file.path}`); + } + } + console.log(''); + console.log('All checks passed!\n'); + process.exit(0); + } else { + // Warning: docs not updated + console.log(chalk.yellow('⚠️ Documentation update recommended but not found')); + console.log(''); + console.log(`${requirement.reason}`); + console.log(`Documentation updated: ${chalk.red('NO')}`); + console.log(''); + + if (requirement.suggestions.length > 0) { + console.log(chalk.cyan('💡 Suggestions:')); + for (const suggestion of requirement.suggestions) { + console.log(` - Update ${suggestion}`); + } + console.log(''); + } + + console.log('Consider updating the relevant documentation.'); + console.log('See docs/development/documentation.md for guidelines.\n'); + process.exit(0); + } + } catch (error: any) { + console.error(chalk.red('❌ Error:'), error.message); + console.error(''); + process.exit(1); + } +} + +// Run main function +if (require.main === module) { + main().catch((error) => { + console.error(chalk.red('Error:'), error); + process.exit(1); + }); +} From 6c16049283fb6819ef604eeac3faa8d9a3752926 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Wed, 31 Dec 2025 17:06:16 +0900 Subject: [PATCH 40/57] docs: add validation workflow in documentation.md --- docs/development/documentation.md | 37 +++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/docs/development/documentation.md b/docs/development/documentation.md index 1dc7f99..5aab061 100644 --- a/docs/development/documentation.md +++ b/docs/development/documentation.md @@ -132,6 +132,42 @@ The basic syntax is: - Keep referenced code snippets short and focused - Update documentation when refactoring referenced code +## When to Update Documentation + +Documentation should be updated whenever user-facing code changes. The project includes an automatic validation script to help ensure documentation stays in sync with code. + +### Code Changes That Require Documentation Updates + +The following changes typically require documentation updates: + +- **CLI changes** (`src/cli/`) → Update `docs/user-guide/cli-usage.md` +- **Public API changes** (`src/index.ts`) → Update `docs/user-guide/` and `docs/architecture/overview.md` +- **Binary changes** (`bin/`) → Update `docs/user-guide/installation.md` +- **Core functionality** (`src/core/`) → Update `docs/architecture/` as needed + +### Validation Workflow + +Before committing changes, validate your documentation updates: + +```bash +npm run docs:validate +``` + +This script will: + +1. Detect whether your branch was created from `main` or `develop` +2. Compare your changes against the base branch +3. Check if documentation was updated when required +4. Provide suggestions for which docs to update + +### Documentation Update Process + +1. **Make code changes** in your feature branch +2. **Update relevant documentation** in `docs/` +3. **Validate documentation** with `npm run docs:validate` +4. **Test CODE_REF references** with `npm run build` (runs coderef validation) +5. **Commit both code and docs** together + ## Common Mistakes to Avoid - Don't use `
` tags (use blank lines) @@ -141,3 +177,4 @@ The basic syntax is: - Don't commit documentation without testing examples - Don't use inline HTML when markdown suffices - Don't forget to update `docs/README.md` when adding new documents +- Don't commit user-facing code changes without updating documentation From 16afd6f69b8a999dfd684aeb0f27676800cde110 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Wed, 31 Dec 2025 17:18:12 +0900 Subject: [PATCH 41/57] docs: add documentation validation command to guides --- CLAUDE.md | 1 + docs/development/getting-started.md | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 271be0a..af6ac37 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,6 +34,7 @@ npm test # Run all tests npm run lint:fix # Auto-fix linting issues npm run format # Format code with Prettier npm run type-check # Run TypeScript compiler checks +npm run docs:validate # Validate documentation updates ``` ### Commit Message Format diff --git a/docs/development/getting-started.md b/docs/development/getting-started.md index 14f190a..892071e 100644 --- a/docs/development/getting-started.md +++ b/docs/development/getting-started.md @@ -28,3 +28,13 @@ Coverage thresholds are set to 80% for all metrics (branches, functions, lines, npx jest src/utils/foo.test.ts # Run specific test file npx jest -t "" # Run tests matching pattern ``` + +## Documentation Validation + +When making changes to user-facing code (CLI, public APIs, etc.), validate that documentation is updated: + +```bash +npm run docs:validate # Check if documentation needs updating +``` + +This script automatically detects your base branch and suggests which documentation files to update. See [Documentation Guidelines](documentation.md) for details. From a065ec926dfe76d00d9a0b7ef4e501c2b701e53d Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Wed, 31 Dec 2025 17:58:53 +0900 Subject: [PATCH 42/57] test: add unit tests for scripts --- jest.config.js | 5 +- scripts/lib/git-diff.test.ts | 283 +++++++++++++++++++++++++++++++ scripts/lib/path-checker.test.ts | 195 +++++++++++++++++++++ scripts/validate-docs.test.ts | 177 +++++++++++++++++++ 4 files changed, 659 insertions(+), 1 deletion(-) create mode 100644 scripts/lib/git-diff.test.ts create mode 100644 scripts/lib/path-checker.test.ts create mode 100644 scripts/validate-docs.test.ts diff --git a/jest.config.js b/jest.config.js index f82910d..cab20d0 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,7 +2,7 @@ module.exports = { preset: 'ts-jest', testEnvironment: 'node', - roots: ['/src'], + roots: ['/src', '/scripts'], testMatch: ['**/*.test.ts'], setupFilesAfterEnv: ['/jest.setup.js'], collectCoverageFrom: [ @@ -10,6 +10,9 @@ module.exports = { '!src/**/*.d.ts', '!src/**/*.test.ts', '!src/cli/**/*.ts', // CLI はカバレッジから除外(統合テストで検証) + 'scripts/**/*.ts', + '!scripts/**/*.test.ts', + '!scripts/lib/types.ts', // 型定義のみのファイルは除外 ], coverageThreshold: { global: { diff --git a/scripts/lib/git-diff.test.ts b/scripts/lib/git-diff.test.ts new file mode 100644 index 0000000..ed1ecf9 --- /dev/null +++ b/scripts/lib/git-diff.test.ts @@ -0,0 +1,283 @@ +import { execSync } from 'child_process'; +import { execGit, detectBaseBranch, getChangedFiles } from './git-diff'; + +// Mock child_process +jest.mock('child_process'); + +const mockedExecSync = execSync as jest.MockedFunction; + +describe('git-diff', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('execGit', () => { + it('should execute git command successfully', () => { + mockedExecSync.mockReturnValue('output\n' as any); + + const result = execGit(['status']); + + expect(result).toBe('output'); + expect(mockedExecSync).toHaveBeenCalledWith('git status', { + encoding: 'utf-8', + stdio: ['pipe', 'pipe', 'pipe'], + }); + }); + + it('should trim whitespace from git command output', () => { + mockedExecSync.mockReturnValue(' output \n' as any); + + const result = execGit(['log']); + + expect(result).toBe('output'); + }); + + it('should throw error when git command fails', () => { + mockedExecSync.mockImplementation(() => { + throw new Error('Command failed'); + }); + + expect(() => execGit(['invalid'])).toThrow('Git command failed'); + }); + + it('should handle multiple arguments', () => { + mockedExecSync.mockReturnValue('result' as any); + + execGit(['diff', '--name-status', 'main...HEAD']); + + expect(mockedExecSync).toHaveBeenCalledWith('git diff --name-status main...HEAD', { + encoding: 'utf-8', + stdio: ['pipe', 'pipe', 'pipe'], + }); + }); + }); + + describe('detectBaseBranch', () => { + it('should throw error when on main branch', () => { + mockedExecSync + .mockReturnValueOnce('main' as any) // getCurrentBranch + .mockReturnValue('dummy' as any); + + expect(() => detectBaseBranch()).toThrow( + 'Cannot validate from main branch. Please run this from a feature branch.' + ); + }); + + it('should throw error when on develop branch', () => { + mockedExecSync + .mockReturnValueOnce('develop' as any) // getCurrentBranch + .mockReturnValue('dummy' as any); + + expect(() => detectBaseBranch()).toThrow( + 'Cannot validate from develop branch. Please run this from a feature branch.' + ); + }); + + it('should throw error when neither main nor develop exists', () => { + mockedExecSync + .mockReturnValueOnce('feature-branch' as any) // getCurrentBranch + .mockImplementationOnce(() => { + throw new Error('Branch not found'); + }) // branchExists(main) + .mockImplementationOnce(() => { + throw new Error('Branch not found'); + }); // branchExists(develop) + + expect(() => detectBaseBranch()).toThrow('Neither main nor develop branch exists'); + }); + + it('should return develop when only develop exists', () => { + mockedExecSync + .mockReturnValueOnce('feature-branch' as any) // getCurrentBranch + .mockImplementationOnce(() => { + throw new Error('Branch not found'); + }) // branchExists(main) + .mockReturnValueOnce('dummy' as any); // branchExists(develop) + + const result = detectBaseBranch(); + + expect(result).toBe('develop'); + }); + + it('should return main when only main exists', () => { + mockedExecSync + .mockReturnValueOnce('feature-branch' as any) // getCurrentBranch + .mockReturnValueOnce('dummy' as any) // branchExists(main) + .mockImplementationOnce(() => { + throw new Error('Branch not found'); + }); // branchExists(develop) + + const result = detectBaseBranch(); + + expect(result).toBe('main'); + }); + + it('should return develop when it has fewer commits', () => { + mockedExecSync + .mockReturnValueOnce('feature-branch' as any) // getCurrentBranch + .mockReturnValueOnce('dummy' as any) // branchExists(main) + .mockReturnValueOnce('dummy' as any) // branchExists(develop) + .mockReturnValueOnce('abc123' as any) // merge-base main + .mockReturnValueOnce('def456' as any) // merge-base develop + .mockReturnValueOnce('5' as any) // countCommits from main + .mockReturnValueOnce('3' as any); // countCommits from develop + + const result = detectBaseBranch(); + + expect(result).toBe('develop'); + }); + + it('should return main when it has fewer commits', () => { + mockedExecSync + .mockReturnValueOnce('feature-branch' as any) // getCurrentBranch + .mockReturnValueOnce('dummy' as any) // branchExists(main) + .mockReturnValueOnce('dummy' as any) // branchExists(develop) + .mockReturnValueOnce('abc123' as any) // merge-base main + .mockReturnValueOnce('def456' as any) // merge-base develop + .mockReturnValueOnce('2' as any) // countCommits from main + .mockReturnValueOnce('5' as any); // countCommits from develop + + const result = detectBaseBranch(); + + expect(result).toBe('main'); + }); + + it('should fallback to develop when merge-base fails', () => { + mockedExecSync + .mockReturnValueOnce('feature-branch' as any) // getCurrentBranch + .mockReturnValueOnce('dummy' as any) // branchExists(main) + .mockReturnValueOnce('dummy' as any) // branchExists(develop) + .mockImplementationOnce(() => { + throw new Error('merge-base failed'); + }); + + const result = detectBaseBranch(); + + expect(result).toBe('develop'); + }); + }); + + describe('getChangedFiles', () => { + it('should return empty result when no changes detected', () => { + mockedExecSync.mockReturnValue('' as any); + + const result = getChangedFiles('main'); + + expect(result).toEqual({ + files: [], + stats: { added: 0, modified: 0, deleted: 0 }, + }); + }); + + it('should parse added files correctly', () => { + mockedExecSync.mockReturnValue('A\tsrc/new-file.ts\n' as any); + + const result = getChangedFiles('main'); + + expect(result.files).toEqual([{ path: 'src/new-file.ts', status: 'A' }]); + expect(result.stats).toEqual({ added: 1, modified: 0, deleted: 0 }); + }); + + it('should parse modified files correctly', () => { + mockedExecSync.mockReturnValue('M\tsrc/existing-file.ts\n' as any); + + const result = getChangedFiles('main'); + + expect(result.files).toEqual([{ path: 'src/existing-file.ts', status: 'M' }]); + expect(result.stats).toEqual({ added: 0, modified: 1, deleted: 0 }); + }); + + it('should parse deleted files correctly', () => { + mockedExecSync.mockReturnValue('D\tsrc/removed-file.ts\n' as any); + + const result = getChangedFiles('main'); + + expect(result.files).toEqual([{ path: 'src/removed-file.ts', status: 'D' }]); + expect(result.stats).toEqual({ added: 0, modified: 0, deleted: 1 }); + }); + + it('should parse renamed files correctly', () => { + mockedExecSync.mockReturnValue('R\tsrc/renamed-file.ts\n' as any); + + const result = getChangedFiles('main'); + + expect(result.files).toEqual([{ path: 'src/renamed-file.ts', status: 'R' }]); + expect(result.stats).toEqual({ added: 0, modified: 0, deleted: 0 }); + }); + + it('should handle multiple files with different statuses', () => { + const diffOutput = [ + 'A\tsrc/new.ts', + 'M\tsrc/modified.ts', + 'D\tsrc/deleted.ts', + 'A\tdocs/README.md', + ].join('\n'); + + mockedExecSync.mockReturnValue(diffOutput as any); + + const result = getChangedFiles('main'); + + expect(result.files).toHaveLength(4); + expect(result.files).toContainEqual({ path: 'src/new.ts', status: 'A' }); + expect(result.files).toContainEqual({ path: 'src/modified.ts', status: 'M' }); + expect(result.files).toContainEqual({ path: 'src/deleted.ts', status: 'D' }); + expect(result.files).toContainEqual({ path: 'docs/README.md', status: 'A' }); + expect(result.stats).toEqual({ added: 2, modified: 1, deleted: 1 }); + }); + + it('should handle file paths with tabs', () => { + mockedExecSync.mockReturnValue('A\tpath\twith\ttabs.ts\n' as any); + + const result = getChangedFiles('main'); + + expect(result.files).toEqual([{ path: 'path\twith\ttabs.ts', status: 'A' }]); + }); + + it('should ignore empty lines', () => { + const diffOutput = 'A\tsrc/file1.ts\n\n\nM\tsrc/file2.ts\n'; + mockedExecSync.mockReturnValue(diffOutput as any); + + const result = getChangedFiles('main'); + + expect(result.files).toHaveLength(2); + }); + + it('should use detectBaseBranch when baseBranch is not provided', () => { + mockedExecSync + .mockReturnValueOnce('feature-branch' as any) // getCurrentBranch + .mockReturnValueOnce('dummy' as any) // branchExists(main) + .mockImplementationOnce(() => { + throw new Error('Branch not found'); + }) // branchExists(develop) + .mockReturnValueOnce('A\tsrc/file.ts\n' as any); // git diff + + const result = getChangedFiles(); + + expect(result.files).toHaveLength(1); + expect(mockedExecSync).toHaveBeenCalledWith( + 'git diff --name-status main...HEAD', + expect.any(Object) + ); + }); + + it('should throw error when git command fails', () => { + mockedExecSync.mockImplementation(() => { + throw new Error('Git error'); + }); + + expect(() => getChangedFiles('main')).toThrow('Failed to get changed files'); + }); + + it('should skip lines without proper format', () => { + const diffOutput = ['A\tsrc/valid.ts', 'INVALID_LINE', 'M\tsrc/another.ts'].join('\n'); + + mockedExecSync.mockReturnValue(diffOutput as any); + + const result = getChangedFiles('main'); + + expect(result.files).toHaveLength(2); + expect(result.files).toContainEqual({ path: 'src/valid.ts', status: 'A' }); + expect(result.files).toContainEqual({ path: 'src/another.ts', status: 'M' }); + }); + }); +}); diff --git a/scripts/lib/path-checker.test.ts b/scripts/lib/path-checker.test.ts new file mode 100644 index 0000000..d6560d5 --- /dev/null +++ b/scripts/lib/path-checker.test.ts @@ -0,0 +1,195 @@ +import { requiresDocsUpdate, hasDocsChanges, suggestDocsToUpdate } from './path-checker'; + +describe('path-checker', () => { + describe('requiresDocsUpdate', () => { + it('should return false when no user-facing code changes are detected', () => { + const changedFiles = ['README.md', 'package.json', 'tsconfig.json']; + + const result = requiresDocsUpdate(changedFiles); + + expect(result.required).toBe(false); + expect(result.reason).toBe('No user-facing code changes detected'); + expect(result.affectedPaths).toEqual([]); + expect(result.suggestions).toEqual([]); + }); + + it('should return true when src/cli/ files are changed', () => { + const changedFiles = ['src/cli/validate.ts', 'src/cli/fix.ts']; + + const result = requiresDocsUpdate(changedFiles); + + expect(result.required).toBe(true); + expect(result.reason).toContain('src/cli/'); + expect(result.affectedPaths).toContain('src/cli/'); + expect(result.suggestions).toContain('docs/user-guide/cli-usage.md'); + }); + + it('should return true when src/index.ts is changed', () => { + const changedFiles = ['src/index.ts']; + + const result = requiresDocsUpdate(changedFiles); + + expect(result.required).toBe(true); + expect(result.reason).toContain('src/index.ts'); + expect(result.affectedPaths).toContain('src/index.ts'); + expect(result.suggestions).toContain('docs/user-guide/'); + expect(result.suggestions).toContain('docs/architecture/overview.md'); + }); + + it('should return true when bin/ files are changed', () => { + const changedFiles = ['bin/coderef.js']; + + const result = requiresDocsUpdate(changedFiles); + + expect(result.required).toBe(true); + expect(result.reason).toContain('bin/'); + expect(result.affectedPaths).toContain('bin/'); + expect(result.suggestions).toContain('docs/user-guide/installation.md'); + }); + + it('should return true when src/core/ files are changed', () => { + const changedFiles = ['src/core/validate.ts']; + + const result = requiresDocsUpdate(changedFiles); + + expect(result.required).toBe(true); + expect(result.reason).toContain('src/core/'); + expect(result.affectedPaths).toContain('src/core/'); + expect(result.suggestions).toContain('docs/architecture/overview.md'); + }); + + it('should handle multiple changed files with different requirements', () => { + const changedFiles = ['src/cli/validate.ts', 'src/index.ts', 'README.md']; + + const result = requiresDocsUpdate(changedFiles); + + expect(result.required).toBe(true); + expect(result.affectedPaths).toContain('src/cli/'); + expect(result.affectedPaths).toContain('src/index.ts'); + expect(result.suggestions).toContain('docs/user-guide/cli-usage.md'); + expect(result.suggestions).toContain('docs/user-guide/'); + }); + + it('should not duplicate affected paths', () => { + const changedFiles = ['src/cli/validate.ts', 'src/cli/fix.ts']; + + const result = requiresDocsUpdate(changedFiles); + + expect(result.affectedPaths).toEqual(['src/cli/']); + expect(result.affectedPaths.length).toBe(1); + }); + + it('should not duplicate suggestions', () => { + const changedFiles = ['src/cli/validate.ts', 'src/cli/fix.ts']; + + const result = requiresDocsUpdate(changedFiles); + + expect(result.suggestions).toEqual(['docs/user-guide/cli-usage.md']); + expect(result.suggestions.length).toBe(1); + }); + + it('should ignore nested paths that do not match required paths', () => { + const changedFiles = ['src/utils/helper.ts', 'src/types/index.ts']; + + const result = requiresDocsUpdate(changedFiles); + + expect(result.required).toBe(false); + }); + }); + + describe('hasDocsChanges', () => { + it('should return true when docs files are changed', () => { + const changedFiles = ['docs/README.md', 'src/index.ts']; + + const result = hasDocsChanges(changedFiles); + + expect(result).toBe(true); + }); + + it('should return false when no docs files are changed', () => { + const changedFiles = ['src/index.ts', 'README.md']; + + const result = hasDocsChanges(changedFiles); + + expect(result).toBe(false); + }); + + it('should return true for nested docs files', () => { + const changedFiles = ['docs/user-guide/cli-usage.md']; + + const result = hasDocsChanges(changedFiles); + + expect(result).toBe(true); + }); + + it('should return false for empty file list', () => { + const changedFiles: string[] = []; + + const result = hasDocsChanges(changedFiles); + + expect(result).toBe(false); + }); + }); + + describe('suggestDocsToUpdate', () => { + it('should return empty array for files that do not require docs updates', () => { + const changedFiles = ['README.md', 'package.json']; + + const result = suggestDocsToUpdate(changedFiles); + + expect(result).toEqual([]); + }); + + it('should suggest docs for src/cli/ changes', () => { + const changedFiles = ['src/cli/validate.ts']; + + const result = suggestDocsToUpdate(changedFiles); + + expect(result).toContain('docs/user-guide/cli-usage.md'); + }); + + it('should suggest docs for src/index.ts changes', () => { + const changedFiles = ['src/index.ts']; + + const result = suggestDocsToUpdate(changedFiles); + + expect(result).toContain('docs/user-guide/'); + expect(result).toContain('docs/architecture/overview.md'); + }); + + it('should suggest docs for bin/ changes', () => { + const changedFiles = ['bin/coderef.js']; + + const result = suggestDocsToUpdate(changedFiles); + + expect(result).toContain('docs/user-guide/installation.md'); + }); + + it('should suggest docs for src/core/ changes', () => { + const changedFiles = ['src/core/validate.ts']; + + const result = suggestDocsToUpdate(changedFiles); + + expect(result).toContain('docs/architecture/overview.md'); + }); + + it('should not duplicate suggestions', () => { + const changedFiles = ['src/cli/validate.ts', 'src/cli/fix.ts']; + + const result = suggestDocsToUpdate(changedFiles); + + expect(result).toEqual(['docs/user-guide/cli-usage.md']); + expect(result.length).toBe(1); + }); + + it('should combine suggestions from multiple files', () => { + const changedFiles = ['src/cli/validate.ts', 'src/index.ts']; + + const result = suggestDocsToUpdate(changedFiles); + + expect(result).toContain('docs/user-guide/cli-usage.md'); + expect(result).toContain('docs/user-guide/'); + expect(result).toContain('docs/architecture/overview.md'); + }); + }); +}); diff --git a/scripts/validate-docs.test.ts b/scripts/validate-docs.test.ts new file mode 100644 index 0000000..4d7de56 --- /dev/null +++ b/scripts/validate-docs.test.ts @@ -0,0 +1,177 @@ +import { detectBaseBranch, getChangedFiles } from './lib/git-diff'; +import { requiresDocsUpdate, hasDocsChanges } from './lib/path-checker'; + +// Mock dependencies +jest.mock('./lib/git-diff'); +jest.mock('./lib/path-checker'); + +const mockedDetectBaseBranch = detectBaseBranch as jest.MockedFunction; +const mockedGetChangedFiles = getChangedFiles as jest.MockedFunction; +const mockedRequiresDocsUpdate = requiresDocsUpdate as jest.MockedFunction< + typeof requiresDocsUpdate +>; +const mockedHasDocsChanges = hasDocsChanges as jest.MockedFunction; + +describe('validate-docs', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('Base branch detection', () => { + it('should exit gracefully when detectBaseBranch throws error', () => { + mockedDetectBaseBranch.mockImplementation(() => { + throw new Error('Cannot validate from main branch. Please run this from a feature branch.'); + }); + + // Since main is not exported and uses require.main check, we test the logic flow + // by verifying the mocked functions are called correctly in the actual implementation + expect(mockedDetectBaseBranch).toBeDefined(); + }); + }); + + describe('getChangedFiles', () => { + it('should handle empty file list', () => { + mockedDetectBaseBranch.mockReturnValue('main'); + mockedGetChangedFiles.mockReturnValue({ + files: [], + stats: { added: 0, modified: 0, deleted: 0 }, + }); + + expect(mockedGetChangedFiles('main')).toEqual({ + files: [], + stats: { added: 0, modified: 0, deleted: 0 }, + }); + }); + + it('should handle file changes', () => { + mockedGetChangedFiles.mockReturnValue({ + files: [ + { path: 'src/index.ts', status: 'M' }, + { path: 'docs/README.md', status: 'M' }, + ], + stats: { added: 0, modified: 2, deleted: 0 }, + }); + + const result = mockedGetChangedFiles('main'); + expect(result.files).toHaveLength(2); + }); + }); + + describe('requiresDocsUpdate', () => { + it('should return false for non-user-facing changes', () => { + mockedRequiresDocsUpdate.mockReturnValue({ + required: false, + reason: 'No user-facing code changes detected', + affectedPaths: [], + suggestions: [], + }); + + const result = mockedRequiresDocsUpdate(['README.md']); + expect(result.required).toBe(false); + }); + + it('should return true for user-facing changes', () => { + mockedRequiresDocsUpdate.mockReturnValue({ + required: true, + reason: 'Changes detected in: src/cli/', + affectedPaths: ['src/cli/'], + suggestions: ['docs/user-guide/cli-usage.md'], + }); + + const result = mockedRequiresDocsUpdate(['src/cli/validate.ts']); + expect(result.required).toBe(true); + expect(result.suggestions).toContain('docs/user-guide/cli-usage.md'); + }); + }); + + describe('hasDocsChanges', () => { + it('should return true when docs are changed', () => { + mockedHasDocsChanges.mockReturnValue(true); + + const result = mockedHasDocsChanges(['docs/README.md']); + expect(result).toBe(true); + }); + + it('should return false when docs are not changed', () => { + mockedHasDocsChanges.mockReturnValue(false); + + const result = mockedHasDocsChanges(['src/index.ts']); + expect(result).toBe(false); + }); + }); + + describe('Integration scenarios', () => { + it('should validate successfully when docs are updated with user-facing changes', () => { + mockedDetectBaseBranch.mockReturnValue('main'); + mockedGetChangedFiles.mockReturnValue({ + files: [ + { path: 'src/cli/validate.ts', status: 'M' }, + { path: 'docs/user-guide/cli-usage.md', status: 'M' }, + ], + stats: { added: 0, modified: 2, deleted: 0 }, + }); + mockedRequiresDocsUpdate.mockReturnValue({ + required: true, + reason: 'Changes detected in: src/cli/', + affectedPaths: ['src/cli/'], + suggestions: ['docs/user-guide/cli-usage.md'], + }); + mockedHasDocsChanges.mockReturnValue(true); + + expect(mockedHasDocsChanges(['src/cli/validate.ts', 'docs/user-guide/cli-usage.md'])).toBe( + true + ); + }); + + it('should show warning when docs are not updated with user-facing changes', () => { + mockedDetectBaseBranch.mockReturnValue('main'); + mockedGetChangedFiles.mockReturnValue({ + files: [{ path: 'src/cli/validate.ts', status: 'M' }], + stats: { added: 0, modified: 1, deleted: 0 }, + }); + mockedRequiresDocsUpdate.mockReturnValue({ + required: true, + reason: 'Changes detected in: src/cli/', + affectedPaths: ['src/cli/'], + suggestions: ['docs/user-guide/cli-usage.md'], + }); + mockedHasDocsChanges.mockReturnValue(false); + + expect(mockedHasDocsChanges(['src/cli/validate.ts'])).toBe(false); + }); + + it('should skip validation for non-user-facing changes', () => { + mockedDetectBaseBranch.mockReturnValue('main'); + mockedGetChangedFiles.mockReturnValue({ + files: [{ path: 'README.md', status: 'M' }], + stats: { added: 0, modified: 1, deleted: 0 }, + }); + mockedRequiresDocsUpdate.mockReturnValue({ + required: false, + reason: 'No user-facing code changes detected', + affectedPaths: [], + suggestions: [], + }); + + const result = mockedRequiresDocsUpdate(['README.md']); + expect(result.required).toBe(false); + }); + + it('should handle files with different statuses', () => { + mockedGetChangedFiles.mockReturnValue({ + files: [ + { path: 'src/new.ts', status: 'A' }, + { path: 'src/modified.ts', status: 'M' }, + { path: 'src/deleted.ts', status: 'D' }, + ], + stats: { added: 1, modified: 1, deleted: 1 }, + }); + + const result = mockedGetChangedFiles('main'); + expect(result.files).toHaveLength(3); + expect(result.stats.added).toBe(1); + expect(result.stats.modified).toBe(1); + expect(result.stats.deleted).toBe(1); + }); + }); +}); From 4b3fc3e08019842cebcee5cadb99ab06e6af78c0 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Wed, 31 Dec 2025 18:09:01 +0900 Subject: [PATCH 43/57] chore: add update-docs command to version control --- .claude/commands/update-docs.md | 66 +++++++++++++++++++++++++++++++++ .gitignore | 3 +- 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 .claude/commands/update-docs.md diff --git a/.claude/commands/update-docs.md b/.claude/commands/update-docs.md new file mode 100644 index 0000000..53b4a1d --- /dev/null +++ b/.claude/commands/update-docs.md @@ -0,0 +1,66 @@ +--- +description: 'Investigate whether documentation updates are needed and update as necessary' +--- + +# Documentation Update Command + +This command automatically updates documentation in response to code changes. + +## Execution Steps + +1. **Run Documentation Validation** + - Run `npm run docs:validate` to check code changes + - Get list of changed files + +2. **Analyze Changes** + - Check specific changes in each file + - Identify important changes that require documentation updates + +3. **Understand Current Documentation Structure** + - Refer to `docs/development/documentation.md` + +4. **Criteria for Changes Requiring Documentation Updates** + - Refer to `docs/development/documentation.md` + +5. **Update Documentation** + - Refer to `docs/development/documentation.md` + - Run `npm run format` to format documentation + +6. **Verify** + - Run `npm run docs:validate` to verify + - Check diff of updated documentation + +7. **Report Results to User** + - List of updated documentation + - Summary of main changes + - List of files to commit + +## Output Format + +Present summary in the following format at the end: + +``` +## Completed! + +### 📝 Updated Documentation + +#### 1. [File Name] (+X lines, -Y lines) +- Change 1 +- Change 2 + +### 🔍 Investigation Results + +[Explanation of main changes] + +### Next Steps + +Review the changes and commit as necessary: +`git commit -m {commit message}` +``` + +## Notes + +- Update CLAUDE.md only when important design policies change (usually only under `docs/`) +- No update needed for minor changes or internal implementation changes +- Type system or API specification changes must be reflected +- Ensure CODE_REF comments reference the latest code diff --git a/.gitignore b/.gitignore index d863396..2a52d57 100644 --- a/.gitignore +++ b/.gitignore @@ -127,7 +127,8 @@ dist .vscode-test # Claude.ai files -.claude/ +.claude/* +!.claude/commands/ # yarn v3 .pnp.* From 4629e12e6fd266e534e4502fbee3c738f0e4c515 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Wed, 31 Dec 2025 18:16:21 +0900 Subject: [PATCH 44/57] docs: add CODE_REF usage guidelines to documentation guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add explicit guidelines for using CODE_REF comments when including code examples from actual source code in documentation. Updates include: - Important notice in Code Blocks section - Expanded Best Practices for CODE_REF with detailed rules - Added to Common Mistakes to Avoid section 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- docs/development/documentation.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/development/documentation.md b/docs/development/documentation.md index 5aab061..0851b1f 100644 --- a/docs/development/documentation.md +++ b/docs/development/documentation.md @@ -56,6 +56,8 @@ For command-line examples, use: - `json` for JSON configuration - `markdown` for markdown examples +**Important**: When including code examples from the actual source code, always use CODE_REF comments to reference the source. This ensures the documentation stays synchronized with the codebase. See [Using CODE_REF in Documentation](#using-code_ref-in-documentation) for details. + ### Lists Use consistent formatting: @@ -128,9 +130,14 @@ The basic syntax is: ### Best Practices for CODE_REF +- **Always use CODE_REF when including code examples from the actual source code** + - This applies to any code snippets copied from `src/`, `bin/`, or other source directories + - CODE_REF ensures documentation stays in sync with the codebase + - Without CODE_REF, code examples can become outdated and misleading - Use CODE_REF when showing actual implementation code - Keep referenced code snippets short and focused - Update documentation when refactoring referenced code +- For hypothetical or generic examples that don't reference actual source code, CODE_REF is not required ## When to Update Documentation @@ -178,3 +185,4 @@ This script will: - Don't use inline HTML when markdown suffices - Don't forget to update `docs/README.md` when adding new documents - Don't commit user-facing code changes without updating documentation +- **Don't include code examples from the actual source code without CODE_REF comments** From 2f66247a1ebeaee68143f9e1d46114bfac4da8a0 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Wed, 31 Dec 2025 20:19:06 +0900 Subject: [PATCH 45/57] fix: preserve error context in git-diff error handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add { cause: error } option to Error constructors in execGit and getChangedFiles functions to preserve original error context for better debugging. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- scripts/lib/git-diff.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/lib/git-diff.ts b/scripts/lib/git-diff.ts index 6ba170f..73d56d1 100644 --- a/scripts/lib/git-diff.ts +++ b/scripts/lib/git-diff.ts @@ -13,7 +13,7 @@ export function execGit(args: string[]): string { }); return result.trim(); } catch (error: any) { - throw new Error(`Git command failed: ${error.message}`); + throw new Error(`Git command failed: ${error.message}`, { cause: error }); } } @@ -133,6 +133,6 @@ export function getChangedFiles(baseBranch?: string): GitDiff { return { files, stats }; } catch (error: any) { - throw new Error(`Failed to get changed files: ${error.message}`); + throw new Error(`Failed to get changed files: ${error.message}`, { cause: error }); } } From 236ad042620d3b6ad9af2f2c57469e4e95f52f5d Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Wed, 31 Dec 2025 20:22:46 +0900 Subject: [PATCH 46/57] feat: add ESLint configuration for scripts directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add TypeScript linting support for scripts/ directory with type checking - Create tsconfig.scripts.json for scripts-specific TypeScript configuration - Update lint scripts in package.json to include all TypeScript files - Fix ESLint errors in scripts: - Replace unused error variable with _error prefix - Remove unnecessary async from main function in validate-docs.ts This ensures code quality standards apply consistently across both src/ and scripts/ directories. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- eslint.config.js | 80 ++++++++++++++++++++++++++++++++++++++++ package.json | 4 +- scripts/lib/git-diff.ts | 2 +- scripts/validate-docs.ts | 7 +--- tsconfig.scripts.json | 10 +++++ 5 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 tsconfig.scripts.json diff --git a/eslint.config.js b/eslint.config.js index 184c862..57506d1 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -99,6 +99,86 @@ module.exports = tseslint.config( }, }, + // Scripts configuration (non-test files with type checking) + { + files: ['scripts/**/*.ts'], + ignores: ['scripts/**/*.test.ts'], + extends: [...tseslint.configs.recommendedTypeChecked, ...tseslint.configs.stylisticTypeChecked], + languageOptions: { + parser: tseslint.parser, + parserOptions: { + project: './tsconfig.scripts.json', + tsconfigRootDir: __dirname, + }, + }, + plugins: { + '@typescript-eslint': tseslint.plugin, + }, + rules: { + // TypeScript-specific rules + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + }, + ], + '@typescript-eslint/no-non-null-assertion': 'warn', + '@typescript-eslint/consistent-type-imports': [ + 'error', + { + prefer: 'type-imports', + }, + ], + + // Relax strict type-checking rules for existing codebase + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/no-unsafe-return': 'off', + '@typescript-eslint/no-unsafe-enum-comparison': 'off', + '@typescript-eslint/prefer-nullish-coalescing': 'warn', + '@typescript-eslint/prefer-optional-chain': 'warn', + '@typescript-eslint/await-thenable': 'warn', + + // General code quality + 'no-console': 'off', // Scripts need console output + 'no-debugger': 'error', + 'no-alert': 'error', + 'prefer-const': 'error', + 'no-var': 'error', + eqeqeq: ['error', 'always', { null: 'ignore' }], + curly: ['error', 'all'], + + // Code style (supplementing Prettier) + 'prefer-arrow-callback': 'error', + 'prefer-template': 'error', + 'object-shorthand': ['error', 'always'], + 'no-useless-concat': 'error', + }, + }, + + // Scripts test files configuration (basic linting without type checking) + { + files: ['scripts/**/*.test.ts'], + extends: [...tseslint.configs.recommended], + languageOptions: { + parser: tseslint.parser, + }, + plugins: { + '@typescript-eslint': tseslint.plugin, + }, + rules: { + '@typescript-eslint/no-empty-function': 'off', // Test mocks often use empty functions + '@typescript-eslint/no-explicit-any': 'off', // Tests can use any for simplicity + }, + }, + // Prettier integration (must be last to disable conflicting rules) prettierConfig ); diff --git a/package.json b/package.json index e33da76..f8885d0 100644 --- a/package.json +++ b/package.json @@ -40,8 +40,8 @@ "test": "jest", "test:watch": "jest --watch", "test:coverage": "jest --coverage", - "lint": "eslint src --ext .ts", - "lint:fix": "eslint src --ext .ts --fix", + "lint": "eslint .", + "lint:fix": "eslint . --fix", "format": "prettier --write \"src/**/*.ts\" \"*.{js,json,md}\"", "format:check": "prettier --check \"src/**/*.ts\" \"*.{js,json,md}\"", "type-check": "tsc --noEmit", diff --git a/scripts/lib/git-diff.ts b/scripts/lib/git-diff.ts index 73d56d1..baedb3a 100644 --- a/scripts/lib/git-diff.ts +++ b/scripts/lib/git-diff.ts @@ -87,7 +87,7 @@ export function detectBaseBranch(): string { // Return the branch with fewer commits (more recent fork point) return commitsFromDevelop <= commitsFromMain ? 'develop' : 'main'; - } catch (error: any) { + } catch (_error: any) { // Fallback to develop if merge-base fails return 'develop'; } diff --git a/scripts/validate-docs.ts b/scripts/validate-docs.ts index 7d8b330..4e283e7 100644 --- a/scripts/validate-docs.ts +++ b/scripts/validate-docs.ts @@ -14,7 +14,7 @@ import { requiresDocsUpdate, hasDocsChanges } from './lib/path-checker'; /** * Main validation function */ -async function main(): Promise { +function main(): void { console.log('🔍 Validating documentation updates...\n'); try { @@ -121,8 +121,5 @@ async function main(): Promise { // Run main function if (require.main === module) { - main().catch((error) => { - console.error(chalk.red('Error:'), error); - process.exit(1); - }); + main(); } diff --git a/tsconfig.scripts.json b/tsconfig.scripts.json new file mode 100644 index 0000000..d694daa --- /dev/null +++ b/tsconfig.scripts.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": "./scripts", + "outDir": "./dist/scripts", + "noEmit": true + }, + "include": ["scripts/**/*"], + "exclude": ["node_modules", "dist"] +} From dd09f3acb6fae6262c2e2a95c486c3de89a8df1a Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Wed, 31 Dec 2025 20:27:22 +0900 Subject: [PATCH 47/57] Revert "fix: preserve error context in git-diff error handling" This reverts commit 2f66247a1ebeaee68143f9e1d46114bfac4da8a0. --- scripts/lib/git-diff.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/lib/git-diff.ts b/scripts/lib/git-diff.ts index baedb3a..d6ba22e 100644 --- a/scripts/lib/git-diff.ts +++ b/scripts/lib/git-diff.ts @@ -13,7 +13,7 @@ export function execGit(args: string[]): string { }); return result.trim(); } catch (error: any) { - throw new Error(`Git command failed: ${error.message}`, { cause: error }); + throw new Error(`Git command failed: ${error.message}`); } } @@ -133,6 +133,6 @@ export function getChangedFiles(baseBranch?: string): GitDiff { return { files, stats }; } catch (error: any) { - throw new Error(`Failed to get changed files: ${error.message}`, { cause: error }); + throw new Error(`Failed to get changed files: ${error.message}`); } } From 105a9c263a5ade19d138a0d5ec020619b62e824e Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Wed, 31 Dec 2025 20:39:12 +0900 Subject: [PATCH 48/57] fix: prevent command injection in git-diff by using spawnSync MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed execGit function from execSync to spawnSync to prevent potential command injection vulnerabilities. This ensures that user input (such as branch names) containing shell metacharacters cannot execute arbitrary commands. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- scripts/lib/git-diff.test.ts | 191 ++++++++++++++++++++--------------- scripts/lib/git-diff.ts | 35 ++++--- 2 files changed, 129 insertions(+), 97 deletions(-) diff --git a/scripts/lib/git-diff.test.ts b/scripts/lib/git-diff.test.ts index ed1ecf9..fa707cd 100644 --- a/scripts/lib/git-diff.test.ts +++ b/scripts/lib/git-diff.test.ts @@ -1,10 +1,10 @@ -import { execSync } from 'child_process'; +import { spawnSync } from 'child_process'; import { execGit, detectBaseBranch, getChangedFiles } from './git-diff'; // Mock child_process jest.mock('child_process'); -const mockedExecSync = execSync as jest.MockedFunction; +const mockedSpawnSync = spawnSync as jest.MockedFunction; describe('git-diff', () => { beforeEach(() => { @@ -13,19 +13,27 @@ describe('git-diff', () => { describe('execGit', () => { it('should execute git command successfully', () => { - mockedExecSync.mockReturnValue('output\n' as any); + mockedSpawnSync.mockReturnValue({ + status: 0, + stdout: 'output\n', + stderr: '', + } as any); const result = execGit(['status']); expect(result).toBe('output'); - expect(mockedExecSync).toHaveBeenCalledWith('git status', { + expect(mockedSpawnSync).toHaveBeenCalledWith('git', ['status'], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], }); }); it('should trim whitespace from git command output', () => { - mockedExecSync.mockReturnValue(' output \n' as any); + mockedSpawnSync.mockReturnValue({ + status: 0, + stdout: ' output \n', + stderr: '', + } as any); const result = execGit(['log']); @@ -33,30 +41,40 @@ describe('git-diff', () => { }); it('should throw error when git command fails', () => { - mockedExecSync.mockImplementation(() => { - throw new Error('Command failed'); - }); + mockedSpawnSync.mockReturnValue({ + status: 1, + stdout: '', + stderr: 'Command failed', + } as any); expect(() => execGit(['invalid'])).toThrow('Git command failed'); }); it('should handle multiple arguments', () => { - mockedExecSync.mockReturnValue('result' as any); + mockedSpawnSync.mockReturnValue({ + status: 0, + stdout: 'result', + stderr: '', + } as any); execGit(['diff', '--name-status', 'main...HEAD']); - expect(mockedExecSync).toHaveBeenCalledWith('git diff --name-status main...HEAD', { - encoding: 'utf-8', - stdio: ['pipe', 'pipe', 'pipe'], - }); + expect(mockedSpawnSync).toHaveBeenCalledWith( + 'git', + ['diff', '--name-status', 'main...HEAD'], + { + encoding: 'utf-8', + stdio: ['pipe', 'pipe', 'pipe'], + } + ); }); }); describe('detectBaseBranch', () => { it('should throw error when on main branch', () => { - mockedExecSync - .mockReturnValueOnce('main' as any) // getCurrentBranch - .mockReturnValue('dummy' as any); + mockedSpawnSync + .mockReturnValueOnce({ status: 0, stdout: 'main', stderr: '' } as any) // getCurrentBranch + .mockReturnValue({ status: 0, stdout: 'dummy', stderr: '' } as any); expect(() => detectBaseBranch()).toThrow( 'Cannot validate from main branch. Please run this from a feature branch.' @@ -64,9 +82,9 @@ describe('git-diff', () => { }); it('should throw error when on develop branch', () => { - mockedExecSync - .mockReturnValueOnce('develop' as any) // getCurrentBranch - .mockReturnValue('dummy' as any); + mockedSpawnSync + .mockReturnValueOnce({ status: 0, stdout: 'develop', stderr: '' } as any) // getCurrentBranch + .mockReturnValue({ status: 0, stdout: 'dummy', stderr: '' } as any); expect(() => detectBaseBranch()).toThrow( 'Cannot validate from develop branch. Please run this from a feature branch.' @@ -74,25 +92,19 @@ describe('git-diff', () => { }); it('should throw error when neither main nor develop exists', () => { - mockedExecSync - .mockReturnValueOnce('feature-branch' as any) // getCurrentBranch - .mockImplementationOnce(() => { - throw new Error('Branch not found'); - }) // branchExists(main) - .mockImplementationOnce(() => { - throw new Error('Branch not found'); - }); // branchExists(develop) + mockedSpawnSync + .mockReturnValueOnce({ status: 0, stdout: 'feature-branch', stderr: '' } as any) // getCurrentBranch + .mockReturnValueOnce({ status: 1, stdout: '', stderr: 'Branch not found' } as any) // branchExists(main) + .mockReturnValueOnce({ status: 1, stdout: '', stderr: 'Branch not found' } as any); // branchExists(develop) expect(() => detectBaseBranch()).toThrow('Neither main nor develop branch exists'); }); it('should return develop when only develop exists', () => { - mockedExecSync - .mockReturnValueOnce('feature-branch' as any) // getCurrentBranch - .mockImplementationOnce(() => { - throw new Error('Branch not found'); - }) // branchExists(main) - .mockReturnValueOnce('dummy' as any); // branchExists(develop) + mockedSpawnSync + .mockReturnValueOnce({ status: 0, stdout: 'feature-branch', stderr: '' } as any) // getCurrentBranch + .mockReturnValueOnce({ status: 1, stdout: '', stderr: 'Branch not found' } as any) // branchExists(main) + .mockReturnValueOnce({ status: 0, stdout: 'dummy', stderr: '' } as any); // branchExists(develop) const result = detectBaseBranch(); @@ -100,12 +112,10 @@ describe('git-diff', () => { }); it('should return main when only main exists', () => { - mockedExecSync - .mockReturnValueOnce('feature-branch' as any) // getCurrentBranch - .mockReturnValueOnce('dummy' as any) // branchExists(main) - .mockImplementationOnce(() => { - throw new Error('Branch not found'); - }); // branchExists(develop) + mockedSpawnSync + .mockReturnValueOnce({ status: 0, stdout: 'feature-branch', stderr: '' } as any) // getCurrentBranch + .mockReturnValueOnce({ status: 0, stdout: 'dummy', stderr: '' } as any) // branchExists(main) + .mockReturnValueOnce({ status: 1, stdout: '', stderr: 'Branch not found' } as any); // branchExists(develop) const result = detectBaseBranch(); @@ -113,14 +123,14 @@ describe('git-diff', () => { }); it('should return develop when it has fewer commits', () => { - mockedExecSync - .mockReturnValueOnce('feature-branch' as any) // getCurrentBranch - .mockReturnValueOnce('dummy' as any) // branchExists(main) - .mockReturnValueOnce('dummy' as any) // branchExists(develop) - .mockReturnValueOnce('abc123' as any) // merge-base main - .mockReturnValueOnce('def456' as any) // merge-base develop - .mockReturnValueOnce('5' as any) // countCommits from main - .mockReturnValueOnce('3' as any); // countCommits from develop + mockedSpawnSync + .mockReturnValueOnce({ status: 0, stdout: 'feature-branch', stderr: '' } as any) // getCurrentBranch + .mockReturnValueOnce({ status: 0, stdout: 'dummy', stderr: '' } as any) // branchExists(main) + .mockReturnValueOnce({ status: 0, stdout: 'dummy', stderr: '' } as any) // branchExists(develop) + .mockReturnValueOnce({ status: 0, stdout: 'abc123', stderr: '' } as any) // merge-base main + .mockReturnValueOnce({ status: 0, stdout: 'def456', stderr: '' } as any) // merge-base develop + .mockReturnValueOnce({ status: 0, stdout: '5', stderr: '' } as any) // countCommits from main + .mockReturnValueOnce({ status: 0, stdout: '3', stderr: '' } as any); // countCommits from develop const result = detectBaseBranch(); @@ -128,14 +138,14 @@ describe('git-diff', () => { }); it('should return main when it has fewer commits', () => { - mockedExecSync - .mockReturnValueOnce('feature-branch' as any) // getCurrentBranch - .mockReturnValueOnce('dummy' as any) // branchExists(main) - .mockReturnValueOnce('dummy' as any) // branchExists(develop) - .mockReturnValueOnce('abc123' as any) // merge-base main - .mockReturnValueOnce('def456' as any) // merge-base develop - .mockReturnValueOnce('2' as any) // countCommits from main - .mockReturnValueOnce('5' as any); // countCommits from develop + mockedSpawnSync + .mockReturnValueOnce({ status: 0, stdout: 'feature-branch', stderr: '' } as any) // getCurrentBranch + .mockReturnValueOnce({ status: 0, stdout: 'dummy', stderr: '' } as any) // branchExists(main) + .mockReturnValueOnce({ status: 0, stdout: 'dummy', stderr: '' } as any) // branchExists(develop) + .mockReturnValueOnce({ status: 0, stdout: 'abc123', stderr: '' } as any) // merge-base main + .mockReturnValueOnce({ status: 0, stdout: 'def456', stderr: '' } as any) // merge-base develop + .mockReturnValueOnce({ status: 0, stdout: '2', stderr: '' } as any) // countCommits from main + .mockReturnValueOnce({ status: 0, stdout: '5', stderr: '' } as any); // countCommits from develop const result = detectBaseBranch(); @@ -143,13 +153,11 @@ describe('git-diff', () => { }); it('should fallback to develop when merge-base fails', () => { - mockedExecSync - .mockReturnValueOnce('feature-branch' as any) // getCurrentBranch - .mockReturnValueOnce('dummy' as any) // branchExists(main) - .mockReturnValueOnce('dummy' as any) // branchExists(develop) - .mockImplementationOnce(() => { - throw new Error('merge-base failed'); - }); + mockedSpawnSync + .mockReturnValueOnce({ status: 0, stdout: 'feature-branch', stderr: '' } as any) // getCurrentBranch + .mockReturnValueOnce({ status: 0, stdout: 'dummy', stderr: '' } as any) // branchExists(main) + .mockReturnValueOnce({ status: 0, stdout: 'dummy', stderr: '' } as any) // branchExists(develop) + .mockReturnValueOnce({ status: 1, stdout: '', stderr: 'merge-base failed' } as any); const result = detectBaseBranch(); @@ -159,7 +167,7 @@ describe('git-diff', () => { describe('getChangedFiles', () => { it('should return empty result when no changes detected', () => { - mockedExecSync.mockReturnValue('' as any); + mockedSpawnSync.mockReturnValue({ status: 0, stdout: '', stderr: '' } as any); const result = getChangedFiles('main'); @@ -170,7 +178,11 @@ describe('git-diff', () => { }); it('should parse added files correctly', () => { - mockedExecSync.mockReturnValue('A\tsrc/new-file.ts\n' as any); + mockedSpawnSync.mockReturnValue({ + status: 0, + stdout: 'A\tsrc/new-file.ts\n', + stderr: '', + } as any); const result = getChangedFiles('main'); @@ -179,7 +191,11 @@ describe('git-diff', () => { }); it('should parse modified files correctly', () => { - mockedExecSync.mockReturnValue('M\tsrc/existing-file.ts\n' as any); + mockedSpawnSync.mockReturnValue({ + status: 0, + stdout: 'M\tsrc/existing-file.ts\n', + stderr: '', + } as any); const result = getChangedFiles('main'); @@ -188,7 +204,11 @@ describe('git-diff', () => { }); it('should parse deleted files correctly', () => { - mockedExecSync.mockReturnValue('D\tsrc/removed-file.ts\n' as any); + mockedSpawnSync.mockReturnValue({ + status: 0, + stdout: 'D\tsrc/removed-file.ts\n', + stderr: '', + } as any); const result = getChangedFiles('main'); @@ -197,7 +217,11 @@ describe('git-diff', () => { }); it('should parse renamed files correctly', () => { - mockedExecSync.mockReturnValue('R\tsrc/renamed-file.ts\n' as any); + mockedSpawnSync.mockReturnValue({ + status: 0, + stdout: 'R\tsrc/renamed-file.ts\n', + stderr: '', + } as any); const result = getChangedFiles('main'); @@ -213,7 +237,7 @@ describe('git-diff', () => { 'A\tdocs/README.md', ].join('\n'); - mockedExecSync.mockReturnValue(diffOutput as any); + mockedSpawnSync.mockReturnValue({ status: 0, stdout: diffOutput, stderr: '' } as any); const result = getChangedFiles('main'); @@ -226,7 +250,11 @@ describe('git-diff', () => { }); it('should handle file paths with tabs', () => { - mockedExecSync.mockReturnValue('A\tpath\twith\ttabs.ts\n' as any); + mockedSpawnSync.mockReturnValue({ + status: 0, + stdout: 'A\tpath\twith\ttabs.ts\n', + stderr: '', + } as any); const result = getChangedFiles('main'); @@ -235,7 +263,7 @@ describe('git-diff', () => { it('should ignore empty lines', () => { const diffOutput = 'A\tsrc/file1.ts\n\n\nM\tsrc/file2.ts\n'; - mockedExecSync.mockReturnValue(diffOutput as any); + mockedSpawnSync.mockReturnValue({ status: 0, stdout: diffOutput, stderr: '' } as any); const result = getChangedFiles('main'); @@ -243,27 +271,24 @@ describe('git-diff', () => { }); it('should use detectBaseBranch when baseBranch is not provided', () => { - mockedExecSync - .mockReturnValueOnce('feature-branch' as any) // getCurrentBranch - .mockReturnValueOnce('dummy' as any) // branchExists(main) - .mockImplementationOnce(() => { - throw new Error('Branch not found'); - }) // branchExists(develop) - .mockReturnValueOnce('A\tsrc/file.ts\n' as any); // git diff + mockedSpawnSync + .mockReturnValueOnce({ status: 0, stdout: 'feature-branch', stderr: '' } as any) // getCurrentBranch + .mockReturnValueOnce({ status: 0, stdout: 'dummy', stderr: '' } as any) // branchExists(main) + .mockReturnValueOnce({ status: 1, stdout: '', stderr: 'Branch not found' } as any) // branchExists(develop) + .mockReturnValueOnce({ status: 0, stdout: 'A\tsrc/file.ts\n', stderr: '' } as any); // git diff const result = getChangedFiles(); expect(result.files).toHaveLength(1); - expect(mockedExecSync).toHaveBeenCalledWith( - 'git diff --name-status main...HEAD', + expect(mockedSpawnSync).toHaveBeenCalledWith( + 'git', + ['diff', '--name-status', 'main...HEAD'], expect.any(Object) ); }); it('should throw error when git command fails', () => { - mockedExecSync.mockImplementation(() => { - throw new Error('Git error'); - }); + mockedSpawnSync.mockReturnValue({ status: 1, stdout: '', stderr: 'Git error' } as any); expect(() => getChangedFiles('main')).toThrow('Failed to get changed files'); }); @@ -271,7 +296,7 @@ describe('git-diff', () => { it('should skip lines without proper format', () => { const diffOutput = ['A\tsrc/valid.ts', 'INVALID_LINE', 'M\tsrc/another.ts'].join('\n'); - mockedExecSync.mockReturnValue(diffOutput as any); + mockedSpawnSync.mockReturnValue({ status: 0, stdout: diffOutput, stderr: '' } as any); const result = getChangedFiles('main'); diff --git a/scripts/lib/git-diff.ts b/scripts/lib/git-diff.ts index d6ba22e..14b3dd8 100644 --- a/scripts/lib/git-diff.ts +++ b/scripts/lib/git-diff.ts @@ -1,20 +1,26 @@ -import { execSync } from 'child_process'; +import { spawnSync } from 'child_process'; import type { GitDiff, GitFileChange } from './types'; /** - * Execute a git command + * Execute a git command safely using spawnSync to prevent command injection */ export function execGit(args: string[]): string { - try { - const result = execSync(`git ${args.join(' ')}`, { - encoding: 'utf-8', - stdio: ['pipe', 'pipe', 'pipe'], - }); - return result.trim(); - } catch (error: any) { - throw new Error(`Git command failed: ${error.message}`); + const result = spawnSync('git', args, { + encoding: 'utf-8', + stdio: ['pipe', 'pipe', 'pipe'], + }); + + if (result.error) { + throw new Error(`Git command failed: ${result.error.message}`); } + + if (result.status !== 0) { + const errorMessage = result.stderr?.trim() || `Command exited with code ${result.status}`; + throw new Error(`Git command failed: ${errorMessage}`); + } + + return result.stdout.trim(); } /** @@ -87,7 +93,7 @@ export function detectBaseBranch(): string { // Return the branch with fewer commits (more recent fork point) return commitsFromDevelop <= commitsFromMain ? 'develop' : 'main'; - } catch (_error: any) { + } catch (_error: unknown) { // Fallback to develop if merge-base fails return 'develop'; } @@ -97,7 +103,7 @@ export function detectBaseBranch(): string { * Get changed files between base branch and HEAD */ export function getChangedFiles(baseBranch?: string): GitDiff { - const base = baseBranch || detectBaseBranch(); + const base = baseBranch ?? detectBaseBranch(); try { // Get diff between base branch and HEAD @@ -132,7 +138,8 @@ export function getChangedFiles(baseBranch?: string): GitDiff { } return { files, stats }; - } catch (error: any) { - throw new Error(`Failed to get changed files: ${error.message}`); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to get changed files: ${message}`); } } From 7b5637644f81e93a7d85c66d2c89dd54e1ac650c Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Wed, 31 Dec 2025 20:44:16 +0900 Subject: [PATCH 49/57] refactor: separate business logic and presentation in validate-docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extracted validation logic into testable validateDocumentation() function that returns structured results, and separated presentation concerns into formatAndDisplay() function. This improves testability, reusability, and maintainability while maintaining full backward compatibility. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- scripts/validate-docs.test.ts | 124 +++++++++++++++++++ scripts/validate-docs.ts | 222 ++++++++++++++++++++++++---------- 2 files changed, 283 insertions(+), 63 deletions(-) diff --git a/scripts/validate-docs.test.ts b/scripts/validate-docs.test.ts index 4d7de56..4f0a4e1 100644 --- a/scripts/validate-docs.test.ts +++ b/scripts/validate-docs.test.ts @@ -1,3 +1,4 @@ +import { validateDocumentation } from './validate-docs'; import { detectBaseBranch, getChangedFiles } from './lib/git-diff'; import { requiresDocsUpdate, hasDocsChanges } from './lib/path-checker'; @@ -174,4 +175,127 @@ describe('validate-docs', () => { expect(result.stats.deleted).toBe(1); }); }); + + describe('validateDocumentation', () => { + it('should return skipped status when detectBaseBranch throws error', () => { + mockedDetectBaseBranch.mockImplementation(() => { + throw new Error('Cannot validate from main branch'); + }); + + const result = validateDocumentation(); + + expect(result.status).toBe('skipped'); + expect(result.skipReason).toBe('Cannot validate from main branch'); + }); + + it('should return skipped status when no changes detected', () => { + mockedDetectBaseBranch.mockReturnValue('main'); + mockedGetChangedFiles.mockReturnValue({ + files: [], + stats: { added: 0, modified: 0, deleted: 0 }, + }); + + const result = validateDocumentation(); + + expect(result.status).toBe('skipped'); + expect(result.skipReason).toBe('No changes detected'); + expect(result.baseBranch).toBe('main'); + }); + + it('should return skipped status when docs update not required', () => { + mockedDetectBaseBranch.mockReturnValue('main'); + mockedGetChangedFiles.mockReturnValue({ + files: [{ path: 'README.md', status: 'M' }], + stats: { added: 0, modified: 1, deleted: 0 }, + }); + mockedRequiresDocsUpdate.mockReturnValue({ + required: false, + reason: 'No user-facing code changes detected', + affectedPaths: [], + suggestions: [], + }); + + const result = validateDocumentation(); + + expect(result.status).toBe('skipped'); + expect(result.skipReason).toBe('Documentation update not required for these changes'); + expect(result.requirement?.required).toBe(false); + }); + + it('should return passed status when docs are updated with user-facing changes', () => { + mockedDetectBaseBranch.mockReturnValue('main'); + mockedGetChangedFiles.mockReturnValue({ + files: [ + { path: 'src/cli/validate.ts', status: 'M' }, + { path: 'docs/user-guide/cli-usage.md', status: 'M' }, + ], + stats: { added: 0, modified: 2, deleted: 0 }, + }); + mockedRequiresDocsUpdate.mockReturnValue({ + required: true, + reason: 'Changes detected in: src/cli/', + affectedPaths: ['src/cli/'], + suggestions: ['docs/user-guide/cli-usage.md'], + }); + mockedHasDocsChanges.mockReturnValue(true); + + const result = validateDocumentation(); + + expect(result.status).toBe('passed'); + expect(result.docsUpdated).toBe(true); + expect(result.requirement?.required).toBe(true); + }); + + it('should return warning status when docs are not updated with user-facing changes', () => { + mockedDetectBaseBranch.mockReturnValue('main'); + mockedGetChangedFiles.mockReturnValue({ + files: [{ path: 'src/cli/validate.ts', status: 'M' }], + stats: { added: 0, modified: 1, deleted: 0 }, + }); + mockedRequiresDocsUpdate.mockReturnValue({ + required: true, + reason: 'Changes detected in: src/cli/', + affectedPaths: ['src/cli/'], + suggestions: ['docs/user-guide/cli-usage.md'], + }); + mockedHasDocsChanges.mockReturnValue(false); + + const result = validateDocumentation(); + + expect(result.status).toBe('warning'); + expect(result.docsUpdated).toBe(false); + expect(result.requirement?.suggestions).toContain('docs/user-guide/cli-usage.md'); + }); + + it('should use provided base branch instead of detecting', () => { + mockedGetChangedFiles.mockReturnValue({ + files: [{ path: 'src/index.ts', status: 'M' }], + stats: { added: 0, modified: 1, deleted: 0 }, + }); + mockedRequiresDocsUpdate.mockReturnValue({ + required: true, + reason: 'Changes detected in: src/', + affectedPaths: ['src/'], + suggestions: [], + }); + mockedHasDocsChanges.mockReturnValue(true); + + const result = validateDocumentation('develop'); + + expect(result.baseBranch).toBe('develop'); + expect(mockedDetectBaseBranch).not.toHaveBeenCalled(); + }); + + it('should return error status when unexpected error occurs', () => { + mockedDetectBaseBranch.mockReturnValue('main'); + mockedGetChangedFiles.mockImplementation(() => { + throw new Error('Git command failed'); + }); + + const result = validateDocumentation(); + + expect(result.status).toBe('error'); + expect(result.error?.message).toBe('Git command failed'); + }); + }); }); diff --git a/scripts/validate-docs.ts b/scripts/validate-docs.ts index 4e283e7..5f29e6a 100644 --- a/scripts/validate-docs.ts +++ b/scripts/validate-docs.ts @@ -8,36 +8,63 @@ import chalk from 'chalk'; +import type { GitDiff, RequirementResult } from './lib/types'; import { detectBaseBranch, getChangedFiles } from './lib/git-diff'; import { requiresDocsUpdate, hasDocsChanges } from './lib/path-checker'; /** - * Main validation function + * Validation result status */ -function main(): void { - console.log('🔍 Validating documentation updates...\n'); +type ValidationStatus = 'skipped' | 'passed' | 'warning' | 'error'; +/** + * Validation result + */ +interface ValidationResult { + status: ValidationStatus; + baseBranch?: string; + diff?: GitDiff; + requirement?: RequirementResult; + docsUpdated?: boolean; + error?: Error; + skipReason?: string; +} + +/** + * Validate documentation updates + * + * This function contains the core validation logic without any presentation concerns. + * It returns a structured result that can be easily tested and reused. + */ +export function validateDocumentation(baseBranch?: string): ValidationResult { try { - // Detect base branch - let baseBranch: string; - try { - baseBranch = detectBaseBranch(); - console.log(`Base branch detected: ${chalk.cyan(baseBranch)}`); - } catch (error: any) { - console.log(chalk.yellow(`⚠️ ${error.message}`)); - console.log('\nValidation skipped.\n'); - process.exit(0); + // Detect base branch if not provided + let detectedBaseBranch: string; + if (baseBranch) { + detectedBaseBranch = baseBranch; + } else { + try { + detectedBaseBranch = detectBaseBranch(); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + return { + status: 'skipped', + skipReason: message, + }; + } } // Get changed files - const diff = getChangedFiles(baseBranch); - console.log(`Comparing: ${chalk.cyan(`${baseBranch}...HEAD`)}\n`); + const diff = getChangedFiles(detectedBaseBranch); // Check if there are any changes if (diff.files.length === 0) { - console.log(chalk.yellow('⚠️ No changes detected\n')); - console.log('Validation skipped.\n'); - process.exit(0); + return { + status: 'skipped', + baseBranch: detectedBaseBranch, + diff, + skipReason: 'No changes detected', + }; } // Get file paths @@ -47,27 +74,90 @@ function main(): void { const requirement = requiresDocsUpdate(changedPaths); if (!requirement.required) { - // No documentation update required + return { + status: 'skipped', + baseBranch: detectedBaseBranch, + diff, + requirement, + skipReason: 'Documentation update not required for these changes', + }; + } + + // Documentation update is required - check if docs have changes + const docsUpdated = hasDocsChanges(changedPaths); + + if (docsUpdated) { + return { + status: 'passed', + baseBranch: detectedBaseBranch, + diff, + requirement, + docsUpdated: true, + }; + } else { + return { + status: 'warning', + baseBranch: detectedBaseBranch, + diff, + requirement, + docsUpdated: false, + }; + } + } catch (error: unknown) { + return { + status: 'error', + error: error instanceof Error ? error : new Error(String(error)), + }; + } +} + +/** + * Format and display validation results + */ +function formatAndDisplay(result: ValidationResult): void { + console.log('🔍 Validating documentation updates...\n'); + + // Handle error status + if (result.status === 'error') { + console.error(chalk.red('❌ Error:'), result.error?.message ?? 'Unknown error'); + console.error(''); + process.exit(1); + } + + // Handle skipped status + if (result.status === 'skipped') { + if (result.skipReason) { + console.log(chalk.yellow(`⚠️ ${result.skipReason}`)); + } + if (result.baseBranch) { + console.log(`Base branch detected: ${chalk.cyan(result.baseBranch)}`); + console.log(`Comparing: ${chalk.cyan(`${result.baseBranch}...HEAD`)}\n`); + } + if (result.diff && result.diff.files.length > 0) { console.log('Changes detected:'); - for (const file of diff.files) { + for (const file of result.diff.files) { const statusIcon = file.status === 'A' ? '+' : file.status === 'M' ? '~' : '-'; const statusColor = file.status === 'A' ? chalk.green : file.status === 'M' ? chalk.yellow : chalk.red; console.log(` ${statusColor(statusIcon)} ${file.path}`); } console.log(''); - console.log(chalk.yellow('⚠️ Documentation update not required for these changes')); - console.log('\nValidation skipped.\n'); - process.exit(0); } + console.log('\nValidation skipped.\n'); + process.exit(0); + } - // Documentation update is required - check if docs have changes - const docsUpdated = hasDocsChanges(changedPaths); + // Display base branch and comparison + if (result.baseBranch) { + console.log(`Base branch detected: ${chalk.cyan(result.baseBranch)}`); + console.log(`Comparing: ${chalk.cyan(`${result.baseBranch}...HEAD`)}\n`); + } - // Display changed files + // Display changed files in user-facing code + if (result.diff && result.requirement) { console.log('Changes detected in user-facing code:'); - for (const file of diff.files) { - if (requirement.affectedPaths.some((path) => file.path.startsWith(path))) { + for (const file of result.diff.files) { + if (result.requirement.affectedPaths.some((path) => file.path.startsWith(path))) { const statusIcon = file.status === 'A' ? '+' : file.status === 'M' ? '~' : '-'; const statusColor = file.status === 'A' ? chalk.green : file.status === 'M' ? chalk.yellow : chalk.red; @@ -75,50 +165,56 @@ function main(): void { } } console.log(''); + } - if (docsUpdated) { - // Success: docs have been updated - console.log(chalk.green('✅ Documentation validation passed')); - console.log(''); - console.log('Documentation updated:'); - for (const file of diff.files) { - if (file.path.startsWith('docs/')) { - const statusIcon = file.status === 'A' ? '+' : file.status === 'M' ? '~' : '-'; - const statusColor = - file.status === 'A' ? chalk.green : file.status === 'M' ? chalk.yellow : chalk.red; - console.log(` ${statusColor(statusIcon)} ${file.path}`); - } + // Handle passed status + if (result.status === 'passed' && result.diff) { + console.log(chalk.green('✅ Documentation validation passed')); + console.log(''); + console.log('Documentation updated:'); + for (const file of result.diff.files) { + if (file.path.startsWith('docs/')) { + const statusIcon = file.status === 'A' ? '+' : file.status === 'M' ? '~' : '-'; + const statusColor = + file.status === 'A' ? chalk.green : file.status === 'M' ? chalk.yellow : chalk.red; + console.log(` ${statusColor(statusIcon)} ${file.path}`); } - console.log(''); - console.log('All checks passed!\n'); - process.exit(0); - } else { - // Warning: docs not updated - console.log(chalk.yellow('⚠️ Documentation update recommended but not found')); - console.log(''); - console.log(`${requirement.reason}`); - console.log(`Documentation updated: ${chalk.red('NO')}`); - console.log(''); + } + console.log(''); + console.log('All checks passed!\n'); + process.exit(0); + } - if (requirement.suggestions.length > 0) { - console.log(chalk.cyan('💡 Suggestions:')); - for (const suggestion of requirement.suggestions) { - console.log(` - Update ${suggestion}`); - } - console.log(''); - } + // Handle warning status + if (result.status === 'warning' && result.requirement) { + console.log(chalk.yellow('⚠️ Documentation update recommended but not found')); + console.log(''); + console.log(`${result.requirement.reason}`); + console.log(`Documentation updated: ${chalk.red('NO')}`); + console.log(''); - console.log('Consider updating the relevant documentation.'); - console.log('See docs/development/documentation.md for guidelines.\n'); - process.exit(0); + if (result.requirement.suggestions.length > 0) { + console.log(chalk.cyan('💡 Suggestions:')); + for (const suggestion of result.requirement.suggestions) { + console.log(` - Update ${suggestion}`); + } + console.log(''); } - } catch (error: any) { - console.error(chalk.red('❌ Error:'), error.message); - console.error(''); - process.exit(1); + + console.log('Consider updating the relevant documentation.'); + console.log('See docs/development/documentation.md for guidelines.\n'); + process.exit(0); } } +/** + * Main validation function + */ +function main(): void { + const result = validateDocumentation(); + formatAndDisplay(result); +} + // Run main function if (require.main === module) { main(); From dbac61383fe0e4dbe6d3654c22615b0bab8235b3 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Wed, 31 Dec 2025 21:01:13 +0900 Subject: [PATCH 50/57] fix: correct collectCoverageFrom pattern order in Jest config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- jest.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jest.config.js b/jest.config.js index cab20d0..1b8cdcc 100644 --- a/jest.config.js +++ b/jest.config.js @@ -7,10 +7,10 @@ module.exports = { setupFilesAfterEnv: ['/jest.setup.js'], collectCoverageFrom: [ 'src/**/*.ts', + 'scripts/**/*.ts', '!src/**/*.d.ts', '!src/**/*.test.ts', '!src/cli/**/*.ts', // CLI はカバレッジから除外(統合テストで検証) - 'scripts/**/*.ts', '!scripts/**/*.test.ts', '!scripts/lib/types.ts', // 型定義のみのファイルは除外 ], From d098259dcecf185e0743baddd0dccc4b960ea471 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Wed, 31 Dec 2025 22:26:14 +0900 Subject: [PATCH 51/57] feat: add variable reference support in CODE_REF MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for referencing variables (const, let, var) in CODE_REF comments using AST-based symbol searching. Features: - Support for const, let, and var declarations - Support for exported variables - Support for destructuring patterns (object, array, rest) - Support for multiple declarators - Include JSDoc comments in extracted code - Function takes precedence when name collision occurs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- README.md | 9 +- src/utils/ast-symbol-search.test.ts | 214 ++++++++++++++++++++++++++++ src/utils/ast-symbol-search.ts | 93 ++++++++++++ src/utils/types.ts | 2 +- 4 files changed, 316 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c105111..7de5964 100644 --- a/README.md +++ b/README.md @@ -67,12 +67,19 @@ Reference code by line numbers: ``` -Reference code by symbol name: +Reference code by symbol name (functions): ```markdown ``` +Reference variables (const, let, var): + +```markdown + + +``` + Reference class methods: ```markdown diff --git a/src/utils/ast-symbol-search.test.ts b/src/utils/ast-symbol-search.test.ts index af70f8c..e889c39 100644 --- a/src/utils/ast-symbol-search.test.ts +++ b/src/utils/ast-symbol-search.test.ts @@ -254,6 +254,220 @@ describe('ast-symbol-search', () => { }); }); + describe('変数検索', () => { + it('トップレベルのconst宣言を検索すること', () => { + const fileContent = ` + /** + * API key constant + */ + const API_KEY = 'test-key'; + `; + + const matches = findSymbolInAST(fileContent, '/test/file.ts', { + memberName: 'API_KEY', + }); + + expect(matches).toHaveLength(1); + expect(matches[0]).toMatchObject({ + memberName: 'API_KEY', + scopeType: 'const', + confidence: 'high', + }); + expect(matches[0].className).toBeUndefined(); + }); + + it('トップレベルのlet宣言を検索すること', () => { + const fileContent = ` + let counter = 0; + `; + + const matches = findSymbolInAST(fileContent, '/test/file.ts', { + memberName: 'counter', + }); + + expect(matches).toHaveLength(1); + expect(matches[0]).toMatchObject({ + memberName: 'counter', + scopeType: 'let', + confidence: 'high', + }); + }); + + it('トップレベルのvar宣言を検索すること', () => { + const fileContent = ` + var globalVar = 'test'; + `; + + const matches = findSymbolInAST(fileContent, '/test/file.ts', { + memberName: 'globalVar', + }); + + expect(matches).toHaveLength(1); + expect(matches[0]).toMatchObject({ + memberName: 'globalVar', + scopeType: 'var', + confidence: 'high', + }); + }); + + it('エクスポートされた変数を検索すること', () => { + const fileContent = ` + /** + * Exported configuration + */ + export const CONFIG = { port: 3000 }; + `; + + const matches = findSymbolInAST(fileContent, '/test/file.ts', { + memberName: 'CONFIG', + }); + + expect(matches).toHaveLength(1); + expect(matches[0]).toMatchObject({ + memberName: 'CONFIG', + scopeType: 'const', + confidence: 'high', + }); + }); + + it('オブジェクトデストラクチャリングから変数を検索すること', () => { + const fileContent = ` + const { API_KEY, SECRET } = process.env; + `; + + const matches = findSymbolInAST(fileContent, '/test/file.ts', { + memberName: 'API_KEY', + }); + + expect(matches).toHaveLength(1); + expect(matches[0]).toMatchObject({ + memberName: 'API_KEY', + scopeType: 'const', + confidence: 'high', + }); + }); + + it('配列デストラクチャリングから変数を検索すること', () => { + const fileContent = ` + const [first, second, third] = items; + `; + + const matches = findSymbolInAST(fileContent, '/test/file.ts', { + memberName: 'second', + }); + + expect(matches).toHaveLength(1); + expect(matches[0]).toMatchObject({ + memberName: 'second', + scopeType: 'const', + confidence: 'high', + }); + }); + + it('オブジェクトのrest構文から変数を検索すること', () => { + const fileContent = ` + const { a, b, ...rest } = obj; + `; + + const matches = findSymbolInAST(fileContent, '/test/file.ts', { + memberName: 'rest', + }); + + expect(matches).toHaveLength(1); + expect(matches[0]).toMatchObject({ + memberName: 'rest', + scopeType: 'const', + confidence: 'high', + }); + }); + + it('配列のrest構文から変数を検索すること', () => { + const fileContent = ` + const [first, ...remaining] = arr; + `; + + const matches = findSymbolInAST(fileContent, '/test/file.ts', { + memberName: 'remaining', + }); + + expect(matches).toHaveLength(1); + expect(matches[0]).toMatchObject({ + memberName: 'remaining', + scopeType: 'const', + confidence: 'high', + }); + }); + + it('複数宣言子を含む文全体を返すこと', () => { + const fileContent = ` + const x = 1, y = 2, z = 3; + `; + + const matches = findSymbolInAST(fileContent, '/test/file.ts', { + memberName: 'y', + }); + + expect(matches).toHaveLength(1); + expect(matches[0]).toMatchObject({ + memberName: 'y', + scopeType: 'const', + confidence: 'high', + }); + // 全体の文が返されることを確認(開始行と終了行が同じ) + expect(matches[0].startLine).toBe(matches[0].endLine); + }); + + it('変数のJSDocコメントを含む行番号を取得すること', () => { + const fileContent = ` + /** + * Multi-line JSDoc for variable + * with detailed description + */ + export const MAX_RETRIES = 3; + `; + + const matches = findSymbolInAST(fileContent, '/test/file.ts', { + memberName: 'MAX_RETRIES', + }); + + expect(matches).toHaveLength(1); + // JSDocの開始行が含まれることを確認 + const fileLines = fileContent.split('\n'); + const jsdocStartLine = fileLines.findIndex((line) => line.includes('/**')); + expect(matches[0].startLine).toBeLessThanOrEqual(jsdocStartLine + 1); // 1-indexed + }); + + it('関数と変数が同じ名前の場合、関数を優先すること', () => { + const fileContent = ` + const config = { port: 3000 }; + function config() { + return { port: 3000 }; + } + `; + + const matches = findSymbolInAST(fileContent, '/test/file.ts', { + memberName: 'config', + }); + + // 関数と変数の両方が見つかる + expect(matches.length).toBeGreaterThanOrEqual(1); + // 最初のマッチは関数であること + expect(matches[0].scopeType).toBe('function'); + }); + + it('変数が存在しない場合は空配列に含まれないこと', () => { + const fileContent = ` + const existingVar = 'test'; + `; + + const matches = findSymbolInAST(fileContent, '/test/file.ts', { + memberName: 'nonExistentVar', + }); + + expect(matches).toHaveLength(0); + }); + }); + describe('ASTキャッシュ', () => { it('同じファイルを複数回パースしないこと', () => { const fileContent = ` diff --git a/src/utils/ast-symbol-search.ts b/src/utils/ast-symbol-search.ts index a9b7aaa..89f62cc 100644 --- a/src/utils/ast-symbol-search.ts +++ b/src/utils/ast-symbol-search.ts @@ -152,6 +152,84 @@ function findFunctionByName( return functions; } +/** + * 変数宣言内に指定された名前の変数が含まれるか + */ +function hasVariableNamed(varDecl: TSESTree.VariableDeclaration, targetName: string): boolean { + for (const declarator of varDecl.declarations) { + // 単純な識別子: const x = 1; + if (declarator.id.type === 'Identifier' && declarator.id.name === targetName) { + return true; + } + + // オブジェクトデストラクチャリング: const { x, y } = obj; + if (declarator.id.type === 'ObjectPattern') { + for (const prop of declarator.id.properties) { + if (prop.type === 'Property' && prop.value.type === 'Identifier') { + if (prop.value.name === targetName) { + return true; + } + } + // RestElement: const { ...rest } = obj; + if (prop.type === 'RestElement' && prop.argument.type === 'Identifier') { + if (prop.argument.name === targetName) { + return true; + } + } + } + } + + // 配列デストラクチャリング: const [x, y] = arr; + if (declarator.id.type === 'ArrayPattern') { + for (const element of declarator.id.elements) { + if (element?.type === 'Identifier' && element.name === targetName) { + return true; + } + // RestElement: const [...rest] = arr; + if (element?.type === 'RestElement' && element.argument.type === 'Identifier') { + if (element.argument.name === targetName) { + return true; + } + } + } + } + } + + return false; +} + +/** + * トップレベル変数を検索 + */ +function findVariableByName( + ast: TSESTree.Program, + variableName: string +): TSESTree.VariableDeclaration[] { + const variables: TSESTree.VariableDeclaration[] = []; + + // トップレベルの変数のみ検索 + for (const statement of ast.body) { + // Case 1: エクスポートされた変数 + if (statement.type === 'ExportNamedDeclaration' && statement.declaration) { + const decl = statement.declaration; + if (decl.type === 'VariableDeclaration') { + if (hasVariableNamed(decl, variableName)) { + variables.push(decl); + } + } + } + + // Case 2: トップレベル変数 + if (statement.type === 'VariableDeclaration') { + if (hasVariableNamed(statement, variableName)) { + variables.push(statement); + } + } + } + + return variables; +} + /** * シンボルパスをパースしてクラス名とメンバー名に分割 */ @@ -224,6 +302,21 @@ export function findSymbolInAST( }); } } + + // 変数を検索(トップレベル) + const variables = findVariableByName(ast, options.memberName); + + for (const varNode of variables) { + if (varNode.loc) { + matches.push({ + memberName: options.memberName, + startLine: getStartLineWithJSDoc(varNode, fileContent), + endLine: varNode.loc.end.line, + scopeType: varNode.kind as 'const' | 'let' | 'var', + confidence: 'high', + }); + } + } } return matches; diff --git a/src/utils/types.ts b/src/utils/types.ts index 1e461d7..bf5ea21 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -138,6 +138,6 @@ export interface SymbolMatch { memberName: string; // メソッド名または関数名 startLine: number; // 開始行番号(JSDocコメント含む) endLine: number; // 終了行番号 - scopeType: 'function' | 'class' | 'method'; // スコープタイプ + scopeType: 'function' | 'class' | 'method' | 'const' | 'let' | 'var'; // スコープタイプ confidence: 'high' | 'medium' | 'low'; // 信頼度 } From 49a8f91ba3e07e5a8d50b5a1ccb430c5f7d5237f Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Wed, 31 Dec 2025 22:32:46 +0900 Subject: [PATCH 52/57] docs: add variable reference documentation Document new variable reference feature in CODE_REF: - Add variable reference syntax and examples - Document supported patterns (const, let, var, destructuring) - Add symbol resolution priority explanation - Update architecture documentation with implementation details --- docs/architecture/code-ref-syntax.md | 40 ++++++++++++-- docs/user-guide/code-ref-syntax.md | 80 ++++++++++++++++++++++++++-- 2 files changed, 114 insertions(+), 6 deletions(-) diff --git a/docs/architecture/code-ref-syntax.md b/docs/architecture/code-ref-syntax.md index c32465d..c465d64 100644 --- a/docs/architecture/code-ref-syntax.md +++ b/docs/architecture/code-ref-syntax.md @@ -2,12 +2,46 @@ ## Supported Reference Patterns -The tool supports three reference patterns: +The tool supports four reference patterns: 1. **Line-based references**: `` -2. **Symbol references**: `` -3. **Class method references**: `` +2. **Function references**: `` +3. **Variable references**: `` +4. **Class method references**: `` ## AST Parsing Uses `@typescript-eslint/typescript-estree` for TypeScript/JavaScript symbol searching and code extraction. + +### Symbol Search Implementation + +Symbol search is implemented in `src/utils/ast-symbol-search.ts`: + +- **Functions**: `findFunctionByName()` searches for top-level and exported function declarations +- **Variables**: `findVariableByName()` searches for top-level and exported variable declarations + - Supports `const`, `let`, and `var` + - Handles destructuring patterns (object, array, rest syntax) + - Returns entire declaration statement for multiple declarators +- **Class Methods**: `findMethodInClass()` searches for methods within a specific class + +### Symbol Resolution Priority + +When multiple symbols match the same name, the search order is: + +1. Class methods (if className is specified) +2. Functions +3. Variables + +This means functions take precedence over variables when both exist with the same name. + +### Variable Declaration Support + +The variable search supports: + +- **Simple declarations**: `const x = 1;` +- **Multiple declarators**: `const x = 1, y = 2;` (entire statement is extracted) +- **Exported variables**: `export const API_KEY = 'key';` +- **Object destructuring**: `const { a, b } = obj;` +- **Array destructuring**: `const [x, y] = arr;` +- **Rest syntax**: `const { ...rest } = obj;`, `const [...items] = arr;` +- **JSDoc comments**: Automatically included in the extracted code diff --git a/docs/user-guide/code-ref-syntax.md b/docs/user-guide/code-ref-syntax.md index 67728ba..dde7754 100644 --- a/docs/user-guide/code-ref-syntax.md +++ b/docs/user-guide/code-ref-syntax.md @@ -16,7 +16,7 @@ This will extract lines 10-20 from `src/index.ts`. ## Reference by Symbol Name -Reference code by function or variable name: +Reference code by function name: ```markdown @@ -24,6 +24,21 @@ Reference code by function or variable name: This will find and extract the `myFunction` function from `src/index.ts` using AST parsing. +## Reference Variables + +Reference variables (const, let, var): + +```markdown + +``` + +This will find and extract the `API_KEY` variable from `src/config.ts` using AST parsing. Supports: + +- `const`, `let`, and `var` declarations +- Exported variables +- Destructuring patterns (object, array, rest syntax) +- Multiple declarators in a single statement + ## Reference Class Methods Reference specific class methods: @@ -118,7 +133,52 @@ getName(): string { ``` ```` -### Example 4: Multiple References in One Document +### Example 4: Variable Reference + +Suppose you have a configuration file `src/config.ts`: + +```typescript +// src/config.ts +/** + * API endpoint URL + */ +export const API_ENDPOINT = 'https://api.example.com'; + +/** + * Maximum retry attempts + */ +export const MAX_RETRIES = 3; + +// Environment variables with destructuring +const { API_KEY, SECRET_KEY } = process.env; +``` + +You can reference variables: + +````markdown +Here's our API endpoint configuration: + + + +```typescript +/** + * API endpoint URL + */ +export const API_ENDPOINT = 'https://api.example.com'; +``` + +Here's a destructured variable: + + + +```typescript +const { API_KEY, SECRET_KEY } = process.env; +``` +```` + +Note: When referencing a destructured variable, the entire destructuring statement is extracted. + +### Example 5: Multiple References in One Document You can have multiple CODE_REF comments in a single markdown file: @@ -149,11 +209,25 @@ getName(): string { ## Best Practices - Use line-based references for stable code sections -- Use symbol references for code that may move around in the file +- Use symbol references (functions/variables) for code that may move around in the file +- Use variable references for configuration constants and important declarations - Keep referenced code blocks focused and readable - Update references when refactoring code - Always include the code block immediately after the CODE_REF comment +## Symbol Resolution Priority + +When a symbol name matches both a function and a variable, the function takes precedence: + +```typescript +const config = { port: 3000 }; // This won't be referenced +function config() { + /* ... */ +} // This will be referenced +``` + +To reference the variable in this case, consider renaming one of them or using line-based references. + ## Common Mistakes ### Missing Code Block From 7b67d19c5e95c70c9139321e1f4865013f92890b Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Wed, 31 Dec 2025 22:40:25 +0900 Subject: [PATCH 53/57] docs: add variable reference documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document new variable reference feature in CODE_REF: - Add variable reference syntax and examples (const, let, var) - Document supported patterns (destructuring, multiple declarators) - Add symbol resolution priority explanation - Document known limitations (nested destructuring, renamed properties, scope) - Update architecture documentation with implementation details 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- docs/architecture/code-ref-syntax.md | 40 ++++++- docs/user-guide/code-ref-syntax.md | 152 ++++++++++++++++++++++++++- 2 files changed, 186 insertions(+), 6 deletions(-) diff --git a/docs/architecture/code-ref-syntax.md b/docs/architecture/code-ref-syntax.md index c32465d..c465d64 100644 --- a/docs/architecture/code-ref-syntax.md +++ b/docs/architecture/code-ref-syntax.md @@ -2,12 +2,46 @@ ## Supported Reference Patterns -The tool supports three reference patterns: +The tool supports four reference patterns: 1. **Line-based references**: `` -2. **Symbol references**: `` -3. **Class method references**: `` +2. **Function references**: `` +3. **Variable references**: `` +4. **Class method references**: `` ## AST Parsing Uses `@typescript-eslint/typescript-estree` for TypeScript/JavaScript symbol searching and code extraction. + +### Symbol Search Implementation + +Symbol search is implemented in `src/utils/ast-symbol-search.ts`: + +- **Functions**: `findFunctionByName()` searches for top-level and exported function declarations +- **Variables**: `findVariableByName()` searches for top-level and exported variable declarations + - Supports `const`, `let`, and `var` + - Handles destructuring patterns (object, array, rest syntax) + - Returns entire declaration statement for multiple declarators +- **Class Methods**: `findMethodInClass()` searches for methods within a specific class + +### Symbol Resolution Priority + +When multiple symbols match the same name, the search order is: + +1. Class methods (if className is specified) +2. Functions +3. Variables + +This means functions take precedence over variables when both exist with the same name. + +### Variable Declaration Support + +The variable search supports: + +- **Simple declarations**: `const x = 1;` +- **Multiple declarators**: `const x = 1, y = 2;` (entire statement is extracted) +- **Exported variables**: `export const API_KEY = 'key';` +- **Object destructuring**: `const { a, b } = obj;` +- **Array destructuring**: `const [x, y] = arr;` +- **Rest syntax**: `const { ...rest } = obj;`, `const [...items] = arr;` +- **JSDoc comments**: Automatically included in the extracted code diff --git a/docs/user-guide/code-ref-syntax.md b/docs/user-guide/code-ref-syntax.md index 67728ba..2ac3c2b 100644 --- a/docs/user-guide/code-ref-syntax.md +++ b/docs/user-guide/code-ref-syntax.md @@ -16,7 +16,7 @@ This will extract lines 10-20 from `src/index.ts`. ## Reference by Symbol Name -Reference code by function or variable name: +Reference code by function name: ```markdown @@ -24,6 +24,21 @@ Reference code by function or variable name: This will find and extract the `myFunction` function from `src/index.ts` using AST parsing. +## Reference Variables + +Reference variables (const, let, var): + +```markdown + +``` + +This will find and extract the `API_KEY` variable from `src/config.ts` using AST parsing. Supports: + +- `const`, `let`, and `var` declarations +- Exported variables +- Destructuring patterns (object, array, rest syntax) +- Multiple declarators in a single statement + ## Reference Class Methods Reference specific class methods: @@ -118,7 +133,52 @@ getName(): string { ``` ```` -### Example 4: Multiple References in One Document +### Example 4: Variable Reference + +Suppose you have a configuration file `src/config.ts`: + +```typescript +// src/config.ts +/** + * API endpoint URL + */ +export const API_ENDPOINT = 'https://api.example.com'; + +/** + * Maximum retry attempts + */ +export const MAX_RETRIES = 3; + +// Environment variables with destructuring +const { API_KEY, SECRET_KEY } = process.env; +``` + +You can reference variables: + +````markdown +Here's our API endpoint configuration: + + + +```typescript +/** + * API endpoint URL + */ +export const API_ENDPOINT = 'https://api.example.com'; +``` + +Here's a destructured variable: + + + +```typescript +const { API_KEY, SECRET_KEY } = process.env; +``` +```` + +Note: When referencing a destructured variable, the entire destructuring statement is extracted. + +### Example 5: Multiple References in One Document You can have multiple CODE_REF comments in a single markdown file: @@ -149,11 +209,97 @@ getName(): string { ## Best Practices - Use line-based references for stable code sections -- Use symbol references for code that may move around in the file +- Use symbol references (functions/variables) for code that may move around in the file +- Use variable references for configuration constants and important declarations - Keep referenced code blocks focused and readable - Update references when refactoring code - Always include the code block immediately after the CODE_REF comment +## Symbol Resolution Priority + +When a symbol name matches both a function and a variable, the function takes precedence: + +```typescript +const config = { port: 3000 }; // This won't be referenced +function config() { + /* ... */ +} // This will be referenced +``` + +To reference the variable in this case, consider renaming one of them or using line-based references. + +## Known Limitations + +### Variable Reference Limitations + +The variable reference feature has the following limitations: + +#### 1. Nested Destructuring Not Supported + +Nested destructuring patterns are not currently supported: + +❌ **Not Supported:** + +```typescript +const { + a: { + b: { c }, + }, +} = obj; +``` + +✅ **Workaround:** Use line-based references for nested destructuring: + +```markdown + +``` + +#### 2. Renamed Destructured Properties Not Supported + +Property renaming in destructuring is not supported: + +❌ **Not Supported:** + +```typescript +const { originalName: renamedVar } = obj; +``` + +Attempting to reference `renamedVar` will not work. + +✅ **Workaround:** Use line-based references or refactor to use the original property name. + +#### 3. Only Top-Level Variables Are Searchable + +Variables declared inside functions, blocks, or other scopes cannot be referenced by symbol: + +❌ **Not Supported:** + +```typescript +function myFunction() { + const localVar = 'value'; // Cannot be referenced +} + +if (condition) { + const blockVar = 'value'; // Cannot be referenced +} +``` + +✅ **Supported:** + +```typescript +const topLevelVar = 'value'; // Can be referenced + +export const exportedVar = 'value'; // Can be referenced +``` + +✅ **Workaround:** For non-top-level variables, use line-based references. + +### General Limitations + +- Symbol references only work with TypeScript/JavaScript files (`.ts`, `.tsx`, `.js`, `.jsx`, `.mjs`, `.cjs`) +- For other file types, use line-based references +- CODE_REF comments must be immediately followed by a code block (empty lines allowed) + ## Common Mistakes ### Missing Code Block From 22b286bf3c23ace38029a2e12ca8b2886a59ddc0 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Wed, 31 Dec 2025 23:00:59 +0900 Subject: [PATCH 54/57] chore: update minimum Node.js version to 20.0.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a70a707..49cdd32 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "prepublishOnly": "npm run build && npm test" }, "engines": { - "node": ">=16.0.0" + "node": ">=20.0.0" }, "publishConfig": { "access": "public" From d184f464f1d3775b276f9de29138c3a84c51cedf Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Wed, 31 Dec 2025 23:03:29 +0900 Subject: [PATCH 55/57] docs: update minimum Node.js version to 20.0.0 --- docs/development/getting-started.md | 2 +- docs/user-guide/installation.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/development/getting-started.md b/docs/development/getting-started.md index 892071e..9264169 100644 --- a/docs/development/getting-started.md +++ b/docs/development/getting-started.md @@ -2,7 +2,7 @@ ## Prerequisites -- **Minimum Node.js version**: 16.0.0 (specified in `package.json` engines) +- **Minimum Node.js version**: 20.0.0 (specified in `package.json` engines) - **Recommended version**: See `.node-version` file in the project root ## Building diff --git a/docs/user-guide/installation.md b/docs/user-guide/installation.md index 8b6a5c0..a799eeb 100644 --- a/docs/user-guide/installation.md +++ b/docs/user-guide/installation.md @@ -2,7 +2,7 @@ ## Requirements -- **Minimum Node.js version**: 16.0.0 +- **Minimum Node.js version**: 20.0.0 - **Recommended version**: See `.node-version` file in the project root ## Install the Package From 5f4cc08e37def57d40d2ff02194ffc4c88331a0d Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Wed, 31 Dec 2025 23:18:32 +0900 Subject: [PATCH 56/57] chore: update minimum Node.js version to 22.0.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- docs/development/getting-started.md | 2 +- docs/user-guide/installation.md | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/development/getting-started.md b/docs/development/getting-started.md index 9264169..7900037 100644 --- a/docs/development/getting-started.md +++ b/docs/development/getting-started.md @@ -2,7 +2,7 @@ ## Prerequisites -- **Minimum Node.js version**: 20.0.0 (specified in `package.json` engines) +- **Minimum Node.js version**: 22.0.0 (specified in `package.json` engines) - **Recommended version**: See `.node-version` file in the project root ## Building diff --git a/docs/user-guide/installation.md b/docs/user-guide/installation.md index a799eeb..f2dea68 100644 --- a/docs/user-guide/installation.md +++ b/docs/user-guide/installation.md @@ -2,7 +2,7 @@ ## Requirements -- **Minimum Node.js version**: 20.0.0 +- **Minimum Node.js version**: 22.0.0 - **Recommended version**: See `.node-version` file in the project root ## Install the Package diff --git a/package.json b/package.json index 49cdd32..f33f0bb 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "prepublishOnly": "npm run build && npm test" }, "engines": { - "node": ">=20.0.0" + "node": ">=22.0.0" }, "publishConfig": { "access": "public" From 87084fac6efe92f1a6e183c845a14c44d7ab9586 Mon Sep 17 00:00:00 2001 From: "m.osumi" Date: Wed, 31 Dec 2025 23:52:46 +0900 Subject: [PATCH 57/57] docs: add CODE_REF usage examples to README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- README.md | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/README.md b/README.md index 7de5964..5fd0bc0 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,83 @@ Reference class methods: ``` +For detailed syntax and options, see [docs/user-guide/code-ref-syntax.md](docs/user-guide/code-ref-syntax.md). + +## Examples + +### Example 1: Documenting API Function Usage + +In your `docs/api.md`: + +````markdown +## Authentication + +Our API uses JWT tokens for authentication. Here's the implementation: + + + +```typescript +export function generateToken(userId: string): string { + // This code will be automatically extracted from src/auth/jwt.ts +} +``` + +Call this function with a user ID to generate a valid token. +```` + +When you run `npx coderef validate`, it will verify that the `generateToken` function exists in `src/auth/jwt.ts` and extract its implementation automatically. + +### Example 2: Keeping Configuration Examples Up-to-Date + +In your `docs/configuration.md`: + +````markdown +## Environment Variables + +Set the following environment variables: + + + +```typescript +export const config = { + apiKey: process.env.API_KEY, + apiUrl: process.env.API_URL, +}; +``` + +These values are loaded at application startup. +```` + +If the `config` object in your source code changes, running `npx coderef fix --auto` will automatically update the documentation to reflect the current implementation. + +### Example 3: Documenting Specific Code Sections + +In your `docs/tutorial.md`: + +````markdown +## Error Handling + +Our error handling middleware is implemented as follows: + + + +```typescript +export function errorHandler(err: Error, req: Request, res: Response) { + console.error(err.stack); + res.status(500).json({ + error: err.message, + timestamp: new Date().toISOString(), + }); +} +``` + +This middleware catches all errors and formats them consistently. +```` + +This references lines 15-35 of the file. If the code changes, `coderef` will detect the mismatch and help you update the reference. + +For more examples and usage patterns, see [docs/user-guide/](docs/user-guide/). + ## Configuration Create `.coderefrc.json` in your project root: