From 4f1d8143f2a3c57aad658b21795c9c29608871e0 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Fri, 6 Mar 2026 20:21:57 -0600 Subject: [PATCH] =?UTF-8?q?feat(wave3):=20add=20@mosaic/quality-rails=20?= =?UTF-8?q?=E2=80=94=20TypeScript=20code=20quality=20scaffolder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Project detection: detectProjectKind (node/python/rust/unknown) - Scaffolder: writes ESLint/Biome/pre-commit configs based on profile - Embedded templates for strict/standard/minimal profiles - CLI: quality-rails init | check | doctor - Depends on @mosaic/types workspace:* --- packages/quality-rails/eslint.config.js | 20 + packages/quality-rails/package.json | 30 + packages/quality-rails/src/cli.ts | 193 +++++ packages/quality-rails/src/detect.ts | 30 + packages/quality-rails/src/index.ts | 5 + packages/quality-rails/src/scaffolder.ts | 201 +++++ packages/quality-rails/src/templates.ts | 182 +++++ packages/quality-rails/src/types.ts | 18 + packages/quality-rails/templates/.gitkeep | 0 packages/quality-rails/tests/detect.test.ts | 40 + .../quality-rails/tests/scaffolder.test.ts | 57 ++ packages/quality-rails/tsconfig.json | 5 + pnpm-lock.yaml | 773 ++++++++++++++++++ 13 files changed, 1554 insertions(+) create mode 100644 packages/quality-rails/eslint.config.js create mode 100644 packages/quality-rails/package.json create mode 100644 packages/quality-rails/src/cli.ts create mode 100644 packages/quality-rails/src/detect.ts create mode 100644 packages/quality-rails/src/index.ts create mode 100644 packages/quality-rails/src/scaffolder.ts create mode 100644 packages/quality-rails/src/templates.ts create mode 100644 packages/quality-rails/src/types.ts create mode 100644 packages/quality-rails/templates/.gitkeep create mode 100644 packages/quality-rails/tests/detect.test.ts create mode 100644 packages/quality-rails/tests/scaffolder.test.ts create mode 100644 packages/quality-rails/tsconfig.json diff --git a/packages/quality-rails/eslint.config.js b/packages/quality-rails/eslint.config.js new file mode 100644 index 0000000..b9b92c4 --- /dev/null +++ b/packages/quality-rails/eslint.config.js @@ -0,0 +1,20 @@ +import tsEslintPlugin from '@typescript-eslint/eslint-plugin'; +import tsEslintParser from '@typescript-eslint/parser'; + +export default [ + { + files: ['src/**/*.ts'], + languageOptions: { + parser: tsEslintParser, + sourceType: 'module', + ecmaVersion: 'latest', + }, + plugins: { + '@typescript-eslint': tsEslintPlugin, + }, + rules: { + 'no-console': 'off', + '@typescript-eslint/no-explicit-any': 'off', + }, + }, +]; diff --git a/packages/quality-rails/package.json b/packages/quality-rails/package.json new file mode 100644 index 0000000..5f508ac --- /dev/null +++ b/packages/quality-rails/package.json @@ -0,0 +1,30 @@ +{ + "name": "@mosaic/quality-rails", + "version": "0.1.0", + "type": "module", + "description": "Mosaic quality rails - TypeScript code quality scaffolder", + "scripts": { + "build": "tsc -p tsconfig.json", + "typecheck": "tsc --noEmit", + "lint": "eslint src/", + "test": "vitest run" + }, + "dependencies": { + "@mosaic/types": "workspace:*", + "commander": "^13", + "js-yaml": "^4" + }, + "devDependencies": { + "@types/node": "^22", + "@types/js-yaml": "^4", + "@typescript-eslint/eslint-plugin": "^8", + "@typescript-eslint/parser": "^8", + "eslint": "^9", + "typescript": "^5", + "vitest": "^2" + }, + "publishConfig": { + "registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm", + "access": "public" + } +} diff --git a/packages/quality-rails/src/cli.ts b/packages/quality-rails/src/cli.ts new file mode 100644 index 0000000..c610bda --- /dev/null +++ b/packages/quality-rails/src/cli.ts @@ -0,0 +1,193 @@ +import { constants } from 'node:fs'; +import { access } from 'node:fs/promises'; +import { resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { Command } from 'commander'; + +import { detectProjectKind } from './detect.js'; +import { scaffoldQualityRails } from './scaffolder.js'; +import type { ProjectKind, QualityProfile, RailsConfig } from './types.js'; + +const VALID_PROFILES: readonly QualityProfile[] = ['strict', 'standard', 'minimal']; + +async function fileExists(filePath: string): Promise { + try { + await access(filePath, constants.F_OK); + return true; + } catch { + return false; + } +} + +function parseProfile(rawProfile: string): QualityProfile { + if (VALID_PROFILES.includes(rawProfile as QualityProfile)) { + return rawProfile as QualityProfile; + } + throw new Error(`Invalid profile: ${rawProfile}. Use one of ${VALID_PROFILES.join(', ')}.`); +} + +function defaultLinters(kind: ProjectKind): string[] { + if (kind === 'node') { + return ['eslint', 'biome']; + } + + if (kind === 'python') { + return ['ruff']; + } + + if (kind === 'rust') { + return ['clippy']; + } + + return []; +} + +function defaultFormatters(kind: ProjectKind): string[] { + if (kind === 'node') { + return ['prettier']; + } + + if (kind === 'python') { + return ['black']; + } + + if (kind === 'rust') { + return ['rustfmt']; + } + + return []; +} + +function expectedFilesForKind(kind: ProjectKind): string[] { + if (kind === 'node') { + return ['.eslintrc', 'biome.json', '.githooks/pre-commit', 'PR-CHECKLIST.md']; + } + + if (kind === 'python') { + return ['pyproject.toml', '.githooks/pre-commit', 'PR-CHECKLIST.md']; + } + + if (kind === 'rust') { + return ['rustfmt.toml', '.githooks/pre-commit', 'PR-CHECKLIST.md']; + } + + return ['.githooks/pre-commit', 'PR-CHECKLIST.md']; +} + +function printScaffoldResult(config: RailsConfig, filesWritten: string[], warnings: string[], commandsToRun: string[]): void { + console.log(`[quality-rails] initialized at ${config.projectPath}`); + console.log(`kind=${config.kind} profile=${config.profile}`); + + if (filesWritten.length > 0) { + console.log('files written:'); + for (const filePath of filesWritten) { + console.log(` - ${filePath}`); + } + } + + if (commandsToRun.length > 0) { + console.log('run next:'); + for (const command of commandsToRun) { + console.log(` - ${command}`); + } + } + + if (warnings.length > 0) { + console.log('warnings:'); + for (const warning of warnings) { + console.log(` - ${warning}`); + } + } +} + +export function createQualityRailsCli(): Command { + const program = new Command('mosaic'); + const qualityRails = program.command('quality-rails').description('Manage quality rails scaffolding'); + + qualityRails + .command('init') + .requiredOption('--project ', 'Project path') + .option('--profile ', 'strict|standard|minimal', 'standard') + .action(async (options: { project: string; profile: string }) => { + const profile = parseProfile(options.profile); + const projectPath = resolve(options.project); + const kind = await detectProjectKind(projectPath); + + const config: RailsConfig = { + projectPath, + kind, + profile, + linters: defaultLinters(kind), + formatters: defaultFormatters(kind), + hooks: true, + }; + + const result = await scaffoldQualityRails(config); + printScaffoldResult(config, result.filesWritten, result.warnings, result.commandsToRun); + }); + + qualityRails + .command('check') + .requiredOption('--project ', 'Project path') + .action(async (options: { project: string }) => { + const projectPath = resolve(options.project); + const kind = await detectProjectKind(projectPath); + const expected = expectedFilesForKind(kind); + const missing: string[] = []; + + for (const relativePath of expected) { + const exists = await fileExists(resolve(projectPath, relativePath)); + if (!exists) { + missing.push(relativePath); + } + } + + if (missing.length > 0) { + console.error('[quality-rails] missing files:'); + for (const relativePath of missing) { + console.error(` - ${relativePath}`); + } + process.exitCode = 1; + return; + } + + console.log(`[quality-rails] all expected files present for ${kind} project`); + }); + + qualityRails + .command('doctor') + .requiredOption('--project ', 'Project path') + .action(async (options: { project: string }) => { + const projectPath = resolve(options.project); + const kind = await detectProjectKind(projectPath); + const expected = expectedFilesForKind(kind); + + console.log(`[quality-rails] doctor for ${projectPath}`); + console.log(`detected project kind: ${kind}`); + + for (const relativePath of expected) { + const exists = await fileExists(resolve(projectPath, relativePath)); + console.log(` - ${exists ? 'ok' : 'missing'}: ${relativePath}`); + } + + if (kind === 'unknown') { + console.log('recommendation: add package.json, pyproject.toml, or Cargo.toml for better defaults.'); + } + }); + + return program; +} + +export async function runQualityRailsCli(argv: string[] = process.argv): Promise { + const program = createQualityRailsCli(); + await program.parseAsync(argv); +} + +const entryPath = process.argv[1] ? resolve(process.argv[1]) : ''; +if (entryPath.length > 0 && entryPath === fileURLToPath(import.meta.url)) { + runQualityRailsCli().catch((error: unknown) => { + console.error(error instanceof Error ? error.message : String(error)); + process.exit(1); + }); +} diff --git a/packages/quality-rails/src/detect.ts b/packages/quality-rails/src/detect.ts new file mode 100644 index 0000000..d7d5393 --- /dev/null +++ b/packages/quality-rails/src/detect.ts @@ -0,0 +1,30 @@ +import { constants } from 'node:fs'; +import { access } from 'node:fs/promises'; +import { join } from 'node:path'; + +import type { ProjectKind } from './types.js'; + +async function fileExists(filePath: string): Promise { + try { + await access(filePath, constants.F_OK); + return true; + } catch { + return false; + } +} + +export async function detectProjectKind(projectPath: string): Promise { + if (await fileExists(join(projectPath, 'package.json'))) { + return 'node'; + } + + if (await fileExists(join(projectPath, 'pyproject.toml'))) { + return 'python'; + } + + if (await fileExists(join(projectPath, 'Cargo.toml'))) { + return 'rust'; + } + + return 'unknown'; +} diff --git a/packages/quality-rails/src/index.ts b/packages/quality-rails/src/index.ts new file mode 100644 index 0000000..3f61eb1 --- /dev/null +++ b/packages/quality-rails/src/index.ts @@ -0,0 +1,5 @@ +export * from './cli.js'; +export * from './detect.js'; +export * from './scaffolder.js'; +export * from './templates.js'; +export * from './types.js'; diff --git a/packages/quality-rails/src/scaffolder.ts b/packages/quality-rails/src/scaffolder.ts new file mode 100644 index 0000000..b9a9527 --- /dev/null +++ b/packages/quality-rails/src/scaffolder.ts @@ -0,0 +1,201 @@ +import { spawn } from 'node:child_process'; +import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises'; +import { dirname, join } from 'node:path'; + +import { + biomeTemplate, + eslintTemplate, + prChecklistTemplate, + preCommitHookTemplate, + pyprojectSection, + rustfmtTemplate, +} from './templates.js'; +import type { RailsConfig, ScaffoldResult } from './types.js'; + +const PYPROJECT_START_MARKER = '# >>> mosaic-quality-rails >>>'; +const PYPROJECT_END_MARKER = '# <<< mosaic-quality-rails <<<'; + +async function ensureDirectory(filePath: string): Promise { + await mkdir(dirname(filePath), { recursive: true }); +} + +async function writeRelativeFile( + projectPath: string, + relativePath: string, + contents: string, + result: ScaffoldResult, +): Promise { + const absolutePath = join(projectPath, relativePath); + await ensureDirectory(absolutePath); + await writeFile(absolutePath, contents, { encoding: 'utf8', mode: 0o644 }); + result.filesWritten.push(relativePath); +} + +async function upsertPyproject( + projectPath: string, + profile: RailsConfig['profile'], + result: ScaffoldResult, +): Promise { + const pyprojectPath = join(projectPath, 'pyproject.toml'); + const nextSection = pyprojectSection(profile); + + let previous = ''; + try { + previous = await readFile(pyprojectPath, 'utf8'); + } catch { + previous = ''; + } + + const existingStart = previous.indexOf(PYPROJECT_START_MARKER); + const existingEnd = previous.indexOf(PYPROJECT_END_MARKER); + + if (existingStart >= 0 && existingEnd > existingStart) { + const before = previous.slice(0, existingStart).trimEnd(); + const after = previous.slice(existingEnd + PYPROJECT_END_MARKER.length).trimStart(); + const rebuilt = [before, nextSection.trim(), after] + .filter((segment) => segment.length > 0) + .join('\n\n'); + await writeRelativeFile(projectPath, 'pyproject.toml', `${rebuilt}\n`, result); + return; + } + + const separator = previous.trim().length > 0 ? '\n\n' : ''; + await writeRelativeFile(projectPath, 'pyproject.toml', `${previous.trimEnd()}${separator}${nextSection}`, result); +} + +function runCommand(command: string, args: string[], cwd: string): Promise { + return new Promise((resolve, reject) => { + const child = spawn(command, args, { + cwd, + stdio: 'ignore', + env: process.env, + }); + + child.on('error', reject); + child.on('exit', (code) => { + if (code === 0) { + resolve(); + return; + } + reject(new Error(`Command failed (${code ?? 'unknown'}): ${command} ${args.join(' ')}`)); + }); + }); +} + +function buildNodeDevDependencies(config: RailsConfig): string[] { + const dependencies = new Set(); + + if (config.linters.some((linter) => linter.toLowerCase() === 'eslint')) { + dependencies.add('eslint'); + dependencies.add('@typescript-eslint/parser'); + dependencies.add('@typescript-eslint/eslint-plugin'); + } + + if (config.linters.some((linter) => linter.toLowerCase() === 'biome')) { + dependencies.add('@biomejs/biome'); + } + + if (config.formatters.some((formatter) => formatter.toLowerCase() === 'prettier')) { + dependencies.add('prettier'); + } + + if (config.hooks) { + dependencies.add('husky'); + } + + return [...dependencies]; +} + +async function installNodeDependencies(config: RailsConfig, result: ScaffoldResult): Promise { + const dependencies = buildNodeDevDependencies(config); + if (dependencies.length === 0) { + return; + } + + const commandLine = `pnpm add -D ${dependencies.join(' ')}`; + + if (process.env.MOSAIC_QUALITY_RAILS_SKIP_INSTALL === '1') { + result.commandsToRun.push(commandLine); + return; + } + + try { + await runCommand('pnpm', ['add', '-D', ...dependencies], config.projectPath); + } catch (error) { + result.warnings.push( + `Failed to auto-install Node dependencies: ${error instanceof Error ? error.message : String(error)}`, + ); + result.commandsToRun.push(commandLine); + } +} + +export async function scaffoldQualityRails(config: RailsConfig): Promise { + const result: ScaffoldResult = { + filesWritten: [], + commandsToRun: [], + warnings: [], + }; + + const normalizedLinters = new Set(config.linters.map((linter) => linter.toLowerCase())); + + if (config.kind === 'node') { + if (normalizedLinters.has('eslint')) { + await writeRelativeFile( + config.projectPath, + '.eslintrc', + eslintTemplate(config.profile), + result, + ); + } + + if (normalizedLinters.has('biome')) { + await writeRelativeFile( + config.projectPath, + 'biome.json', + biomeTemplate(config.profile), + result, + ); + } + + await installNodeDependencies(config, result); + } + + if (config.kind === 'python') { + await upsertPyproject(config.projectPath, config.profile, result); + } + + if (config.kind === 'rust') { + await writeRelativeFile( + config.projectPath, + 'rustfmt.toml', + rustfmtTemplate(config.profile), + result, + ); + } + + if (config.hooks) { + await writeRelativeFile( + config.projectPath, + '.githooks/pre-commit', + preCommitHookTemplate(config), + result, + ); + await chmod(join(config.projectPath, '.githooks/pre-commit'), 0o755); + result.commandsToRun.push('git config core.hooksPath .githooks'); + } + + await writeRelativeFile( + config.projectPath, + 'PR-CHECKLIST.md', + prChecklistTemplate(config.profile), + result, + ); + + if (config.kind === 'unknown') { + result.warnings.push( + 'Unable to detect project kind. Generated generic rails only (hooks + PR checklist).', + ); + } + + return result; +} diff --git a/packages/quality-rails/src/templates.ts b/packages/quality-rails/src/templates.ts new file mode 100644 index 0000000..2f52f53 --- /dev/null +++ b/packages/quality-rails/src/templates.ts @@ -0,0 +1,182 @@ +import type { QualityProfile, RailsConfig } from './types.js'; + +const PROFILE_TO_MAX_WARNINGS: Record = { + strict: 0, + standard: 10, + minimal: 50, +}; + +const PROFILE_TO_LINE_LENGTH: Record = { + strict: 100, + standard: 110, + minimal: 120, +}; + +export function eslintTemplate(profile: QualityProfile): string { + return `${JSON.stringify( + { + root: true, + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint'], + extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], + env: { + node: true, + es2022: true, + }, + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + }, + rules: { + 'no-console': profile === 'strict' ? 'error' : 'warn', + '@typescript-eslint/no-explicit-any': + profile === 'minimal' ? 'off' : profile === 'strict' ? 'error' : 'warn', + '@typescript-eslint/explicit-function-return-type': + profile === 'strict' ? 'warn' : 'off', + 'max-lines-per-function': [ + profile === 'minimal' ? 'off' : 'warn', + { + max: profile === 'strict' ? 60 : 100, + skipBlankLines: true, + skipComments: true, + }, + ], + }, + }, + null, + 2, + )}\n`; +} + +export function biomeTemplate(profile: QualityProfile): string { + return `${JSON.stringify( + { + '$schema': 'https://biomejs.dev/schemas/1.8.3/schema.json', + formatter: { + enabled: true, + indentStyle: 'space', + indentWidth: 2, + lineWidth: PROFILE_TO_LINE_LENGTH[profile], + }, + linter: { + enabled: true, + rules: { + recommended: true, + suspicious: { + noConsole: profile === 'strict' ? 'error' : 'warn', + }, + complexity: { + noExcessiveCognitiveComplexity: + profile === 'strict' ? 'warn' : profile === 'standard' ? 'info' : 'off', + }, + }, + }, + javascript: { + formatter: { + quoteStyle: 'single', + trailingCommas: 'all', + }, + }, + }, + null, + 2, + )}\n`; +} + +export function pyprojectSection(profile: QualityProfile): string { + const lineLength = PROFILE_TO_LINE_LENGTH[profile]; + return [ + '# >>> mosaic-quality-rails >>>', + '[tool.ruff]', + `line-length = ${lineLength}`, + 'target-version = "py311"', + '', + '[tool.ruff.lint]', + 'select = ["E", "F", "I", "UP", "B"]', + `ignore = ${profile === 'minimal' ? '[]' : '["E501"]'}`, + '', + '[tool.black]', + `line-length = ${lineLength}`, + '', + '# <<< mosaic-quality-rails <<<', + '', + ].join('\n'); +} + +export function rustfmtTemplate(profile: QualityProfile): string { + const maxWidth = PROFILE_TO_LINE_LENGTH[profile]; + const useSmallHeuristics = profile === 'strict' ? 'Max' : 'Default'; + + return [ + `max_width = ${maxWidth}`, + `use_small_heuristics = "${useSmallHeuristics}"`, + `imports_granularity = "${profile === 'minimal' ? 'Crate' : 'Module'}"`, + `group_imports = "${profile === 'strict' ? 'StdExternalCrate' : 'Preserve'}"`, + '', + ].join('\n'); +} + +function resolveHookCommands(config: RailsConfig): string[] { + const commands: string[] = []; + + if (config.kind === 'node') { + if (config.linters.some((linter) => linter.toLowerCase() === 'eslint')) { + commands.push('pnpm lint'); + } + if (config.linters.some((linter) => linter.toLowerCase() === 'biome')) { + commands.push('pnpm biome check .'); + } + if (config.formatters.some((formatter) => formatter.toLowerCase() === 'prettier')) { + commands.push('pnpm prettier --check .'); + } + commands.push('pnpm test --if-present'); + } + + if (config.kind === 'python') { + commands.push('ruff check .'); + commands.push('black --check .'); + } + + if (config.kind === 'rust') { + commands.push('cargo fmt --check'); + commands.push('cargo clippy --all-targets --all-features -- -D warnings'); + } + + if (commands.length === 0) { + commands.push('echo "No quality commands configured for this project kind"'); + } + + return commands; +} + +export function preCommitHookTemplate(config: RailsConfig): string { + const commands = resolveHookCommands(config) + .map((command) => `${command} || exit 1`) + .join('\n'); + + return [ + '#!/usr/bin/env sh', + 'set -eu', + '', + 'echo "[quality-rails] Running pre-commit checks..."', + commands, + 'echo "[quality-rails] Checks passed."', + '', + ].join('\n'); +} + +export function prChecklistTemplate(profile: QualityProfile): string { + return [ + '# Code Review Checklist', + '', + `Profile: **${profile}**`, + '', + '- [ ] Requirements mapped to tests', + '- [ ] Error handling covers unhappy paths', + '- [ ] Lint and typecheck are clean', + '- [ ] Test suite passes', + '- [ ] Security-sensitive paths reviewed', + `- [ ] Warnings count <= ${PROFILE_TO_MAX_WARNINGS[profile]}`, + '', + ].join('\n'); +} diff --git a/packages/quality-rails/src/types.ts b/packages/quality-rails/src/types.ts new file mode 100644 index 0000000..b487c5d --- /dev/null +++ b/packages/quality-rails/src/types.ts @@ -0,0 +1,18 @@ +export type ProjectKind = 'node' | 'python' | 'rust' | 'unknown'; + +export type QualityProfile = 'strict' | 'standard' | 'minimal'; + +export interface RailsConfig { + projectPath: string; + kind: ProjectKind; + profile: QualityProfile; + linters: string[]; + formatters: string[]; + hooks: boolean; +} + +export interface ScaffoldResult { + filesWritten: string[]; + commandsToRun: string[]; + warnings: string[]; +} diff --git a/packages/quality-rails/templates/.gitkeep b/packages/quality-rails/templates/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/quality-rails/tests/detect.test.ts b/packages/quality-rails/tests/detect.test.ts new file mode 100644 index 0000000..389be32 --- /dev/null +++ b/packages/quality-rails/tests/detect.test.ts @@ -0,0 +1,40 @@ +import { mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +import { describe, expect, it } from 'vitest'; + +import { detectProjectKind } from '../src/detect.js'; + +async function withTempDir(run: (directory: string) => Promise): Promise { + const directory = await mkdtemp(join(tmpdir(), 'quality-rails-detect-')); + try { + await run(directory); + } finally { + await rm(directory, { recursive: true, force: true }); + } +} + +describe('detectProjectKind', () => { + it('returns node when package.json exists', async () => { + await withTempDir(async (directory) => { + await writeFile(join(directory, 'package.json'), '{"name":"fixture"}\n', 'utf8'); + + await expect(detectProjectKind(directory)).resolves.toBe('node'); + }); + }); + + it('returns python when pyproject.toml exists and package.json does not', async () => { + await withTempDir(async (directory) => { + await writeFile(join(directory, 'pyproject.toml'), '[project]\nname = "fixture"\n', 'utf8'); + + await expect(detectProjectKind(directory)).resolves.toBe('python'); + }); + }); + + it('returns unknown when no known project files exist', async () => { + await withTempDir(async (directory) => { + await expect(detectProjectKind(directory)).resolves.toBe('unknown'); + }); + }); +}); diff --git a/packages/quality-rails/tests/scaffolder.test.ts b/packages/quality-rails/tests/scaffolder.test.ts new file mode 100644 index 0000000..ab2bd8c --- /dev/null +++ b/packages/quality-rails/tests/scaffolder.test.ts @@ -0,0 +1,57 @@ +import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +import { describe, expect, it } from 'vitest'; + +import { scaffoldQualityRails } from '../src/scaffolder.js'; +import type { RailsConfig } from '../src/types.js'; + +async function withTempDir(run: (directory: string) => Promise): Promise { + const directory = await mkdtemp(join(tmpdir(), 'quality-rails-scaffold-')); + try { + await run(directory); + } finally { + await rm(directory, { recursive: true, force: true }); + } +} + +describe('scaffoldQualityRails', () => { + it('writes expected node quality rails files', async () => { + await withTempDir(async (directory) => { + await writeFile(join(directory, 'package.json'), '{"name":"fixture"}\n', 'utf8'); + + const previous = process.env.MOSAIC_QUALITY_RAILS_SKIP_INSTALL; + process.env.MOSAIC_QUALITY_RAILS_SKIP_INSTALL = '1'; + + const config: RailsConfig = { + projectPath: directory, + kind: 'node', + profile: 'strict', + linters: ['eslint', 'biome'], + formatters: ['prettier'], + hooks: true, + }; + + const result = await scaffoldQualityRails(config); + + process.env.MOSAIC_QUALITY_RAILS_SKIP_INSTALL = previous; + + await expect(readFile(join(directory, '.eslintrc'), 'utf8')).resolves.toContain('parser'); + await expect(readFile(join(directory, 'biome.json'), 'utf8')).resolves.toContain('"formatter"'); + await expect(readFile(join(directory, '.githooks', 'pre-commit'), 'utf8')).resolves.toContain('pnpm lint'); + await expect(readFile(join(directory, 'PR-CHECKLIST.md'), 'utf8')).resolves.toContain('Code Review Checklist'); + + expect(result.filesWritten).toEqual( + expect.arrayContaining([ + '.eslintrc', + 'biome.json', + '.githooks/pre-commit', + 'PR-CHECKLIST.md', + ]), + ); + expect(result.commandsToRun).toContain('git config core.hooksPath .githooks'); + expect(result.warnings).toHaveLength(0); + }); + }); +}); diff --git a/packages/quality-rails/tsconfig.json b/packages/quality-rails/tsconfig.json new file mode 100644 index 0000000..972facb --- /dev/null +++ b/packages/quality-rails/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { "outDir": "dist", "rootDir": "src" }, + "include": ["src"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f38d515..2fe589e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,6 +83,40 @@ importers: specifier: ^2 version: 2.1.9(@types/node@22.19.15) + packages/quality-rails: + dependencies: + '@mosaic/types': + specifier: workspace:* + version: link:../types + commander: + specifier: ^13 + version: 13.1.0 + js-yaml: + specifier: ^4 + version: 4.1.1 + devDependencies: + '@types/js-yaml': + specifier: ^4 + version: 4.0.9 + '@types/node': + specifier: ^22 + version: 22.19.15 + '@typescript-eslint/eslint-plugin': + specifier: ^8 + version: 8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': + specifier: ^8 + version: 8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + eslint: + specifier: ^9 + version: 9.39.4(jiti@2.6.1) + typescript: + specifier: ^5 + version: 5.9.3 + vitest: + specifier: ^2 + version: 2.1.9(@types/node@22.19.15) + packages/queue: dependencies: '@modelcontextprotocol/sdk': @@ -518,12 +552,66 @@ packages: cpu: [x64] os: [win32] + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.5': + resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.4': + resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@hono/node-server@1.19.11': resolution: {integrity: sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==} engines: {node: '>=18.14.1'} peerDependencies: hono: ^4 + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + '@inquirer/external-editor@1.0.3': resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} engines: {node: '>=18'} @@ -818,12 +906,74 @@ packages: '@types/js-yaml@4.0.9': resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} '@types/node@22.19.15': resolution: {integrity: sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==} + '@typescript-eslint/eslint-plugin@8.56.1': + resolution: {integrity: sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.56.1 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.56.1': + resolution: {integrity: sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.56.1': + resolution: {integrity: sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.56.1': + resolution: {integrity: sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.56.1': + resolution: {integrity: sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.56.1': + resolution: {integrity: sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.56.1': + resolution: {integrity: sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.56.1': + resolution: {integrity: sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.56.1': + resolution: {integrity: sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.56.1': + resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vitest/expect@2.1.9': resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} @@ -886,6 +1036,16 @@ packages: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + ajv-formats@3.0.1: resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} peerDependencies: @@ -894,6 +1054,9 @@ packages: ajv: optional: true + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + ajv@8.18.0: resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} @@ -905,6 +1068,10 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + ansis@4.2.0: resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} engines: {node: '>=14'} @@ -927,6 +1094,13 @@ packages: resolution: {integrity: sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==} engines: {node: '>=20.19.0'} + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} @@ -938,6 +1112,13 @@ packages: resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} engines: {node: '>=18'} + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@5.0.4: + resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} + engines: {node: 18 || 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -958,10 +1139,18 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + chai@5.3.3: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + chardet@2.1.1: resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} @@ -977,6 +1166,13 @@ packages: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + commander@13.1.0: resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} engines: {node: '>=18'} @@ -985,6 +1181,9 @@ packages: resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} engines: {node: '>=20'} + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + content-disposition@1.0.1: resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} engines: {node: '>=18'} @@ -1022,6 +1221,9 @@ packages: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} @@ -1101,14 +1303,64 @@ packages: escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@9.39.4: + resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} hasBin: true + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} @@ -1145,6 +1397,12 @@ packages: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} @@ -1160,6 +1418,10 @@ packages: picomatch: optional: true + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -1172,6 +1434,17 @@ packages: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.4: + resolution: {integrity: sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==} + forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -1211,6 +1484,14 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} @@ -1222,6 +1503,10 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -1253,6 +1538,18 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -1317,25 +1614,48 @@ packages: engines: {node: '>=6'} hasBin: true + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} json-schema-typed@8.0.2: resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + lodash.defaults@4.2.0: resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} lodash.isarguments@3.1.0: resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.startcase@4.4.0: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} @@ -1373,6 +1693,13 @@ packages: resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} engines: {node: '>=18'} + minimatch@10.2.4: + resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} + engines: {node: 18 || 20 || >=22} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -1385,6 +1712,9 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + negotiator@1.0.0: resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} @@ -1404,6 +1734,10 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} @@ -1415,10 +1749,18 @@ packages: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + p-map@2.1.0: resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} engines: {node: '>=6'} @@ -1430,6 +1772,10 @@ packages: package-manager-detector@0.2.11: resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -1482,6 +1828,10 @@ packages: resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} engines: {node: ^10 || ^12 || >=14} + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + prettier@2.8.8: resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} engines: {node: '>=10.13.0'} @@ -1496,6 +1846,10 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + qs@6.15.0: resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} engines: {node: '>=0.6'} @@ -1537,6 +1891,10 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} @@ -1669,9 +2027,17 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + strip-literal@3.1.0: resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} @@ -1718,6 +2084,12 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + ts-api-utils@2.4.0: + resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + tsdown@0.12.9: resolution: {integrity: sha512-MfrXm9PIlT3saovtWKf/gCJJ/NQCdE0SiREkdNC+9Qy6UHhdeDPxnkFaBD7xttVUmgp0yUHtGirpoLB+OVLuLA==} engines: {node: '>=18.0.0'} @@ -1782,6 +2154,10 @@ packages: resolution: {integrity: sha512-UCTxeMNYT1cKaHiIFdLCQ7ulI+jw5i5uOnJOrRXsgUD7G3+OjlUjwVd7JfeVt2McWSVGjYA3EVW/v1FSsJ5DtA==} hasBin: true + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + type-is@2.0.1: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} @@ -1808,6 +2184,9 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -1956,6 +2335,10 @@ packages: engines: {node: '>=8'} hasBin: true + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -1964,6 +2347,10 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + zod-to-json-schema@3.25.1: resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} peerDependencies: @@ -2317,10 +2704,67 @@ snapshots: '@esbuild/win32-x64@0.27.3': optional: true + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.6.1))': + dependencies: + eslint: 9.39.4(jiti@2.6.1) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.2': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.5': + dependencies: + ajv: 6.14.0 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.4': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + '@hono/node-server@1.19.11(hono@4.12.5)': dependencies: hono: 4.12.5 + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + '@inquirer/external-editor@1.0.3(@types/node@22.19.15)': dependencies: chardet: 2.1.1 @@ -2547,12 +2991,105 @@ snapshots: '@types/js-yaml@4.0.9': {} + '@types/json-schema@7.0.15': {} + '@types/node@12.20.55': {} '@types/node@22.19.15': dependencies: undici-types: 6.21.0 + '@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/type-utils': 8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.56.1 + eslint: 9.39.4(jiti@2.6.1) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.56.1 + debug: 4.4.3 + eslint: 9.39.4(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.56.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) + '@typescript-eslint/types': 8.56.1 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.56.1': + dependencies: + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/visitor-keys': 8.56.1 + + '@typescript-eslint/tsconfig-utils@8.56.1(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.4(jiti@2.6.1) + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.56.1': {} + + '@typescript-eslint/typescript-estree@8.56.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.56.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/visitor-keys': 8.56.1 + debug: 4.4.3 + minimatch: 10.2.4 + semver: 7.7.4 + tinyglobby: 0.2.15 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.56.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) + eslint: 9.39.4(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.56.1': + dependencies: + '@typescript-eslint/types': 8.56.1 + eslint-visitor-keys: 5.0.1 + '@vitest/expect@2.1.9': dependencies: '@vitest/spy': 2.1.9 @@ -2640,10 +3177,23 @@ snapshots: mime-types: 3.0.2 negotiator: 1.0.0 + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + ajv-formats@3.0.1(ajv@8.18.0): optionalDependencies: ajv: 8.18.0 + ajv@6.14.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 @@ -2655,6 +3205,10 @@ snapshots: ansi-regex@5.0.1: {} + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + ansis@4.2.0: {} argparse@1.0.10: @@ -2672,6 +3226,10 @@ snapshots: '@babel/parser': 7.29.0 pathe: 2.0.3 + balanced-match@1.0.2: {} + + balanced-match@4.0.4: {} + better-path-resolve@1.0.0: dependencies: is-windows: 1.0.2 @@ -2692,6 +3250,15 @@ snapshots: transitivePeerDependencies: - supports-color + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@5.0.4: + dependencies: + balanced-match: 4.0.4 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -2710,6 +3277,8 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 + callsites@3.1.0: {} + chai@5.3.3: dependencies: assertion-error: 2.0.1 @@ -2718,6 +3287,11 @@ snapshots: loupe: 3.2.1 pathval: 2.0.1 + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + chardet@2.1.1: {} check-error@2.1.3: {} @@ -2728,10 +3302,18 @@ snapshots: cluster-key-slot@1.1.2: {} + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + commander@13.1.0: {} commander@14.0.3: {} + concat-map@0.0.1: {} + content-disposition@1.0.1: {} content-type@1.0.5: {} @@ -2757,6 +3339,8 @@ snapshots: deep-eql@5.0.2: {} + deep-is@0.1.4: {} + defu@6.1.4: {} denque@2.1.0: {} @@ -2857,12 +3441,84 @@ snapshots: escape-html@1.0.3: {} + escape-string-regexp@4.0.0: {} + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@9.39.4(jiti@2.6.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.2 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.5 + '@eslint/js': 9.39.4 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.14.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.6.1 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 4.2.1 + esprima@4.0.1: {} + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 + esutils@2.0.3: {} + etag@1.8.1: {} eventsource-parser@3.0.6: {} @@ -2923,6 +3579,10 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + fast-uri@3.1.0: {} fastq@1.20.1: @@ -2933,6 +3593,10 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -2953,6 +3617,18 @@ snapshots: locate-path: 5.0.0 path-exists: 4.0.0 + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.4 + keyv: 4.5.4 + + flatted@3.3.4: {} + forwarded@0.2.0: {} fresh@2.0.0: {} @@ -3000,6 +3676,12 @@ snapshots: dependencies: is-glob: 4.0.3 + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + globby@11.1.0: dependencies: array-union: 2.1.0 @@ -3013,6 +3695,8 @@ snapshots: graceful-fs@4.2.11: {} + has-flag@4.0.0: {} + has-symbols@1.1.0: {} hasown@2.0.2: @@ -3039,6 +3723,15 @@ snapshots: ignore@5.3.2: {} + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + inherits@2.0.4: {} ioredis@5.10.0: @@ -3094,22 +3787,43 @@ snapshots: jsesc@3.1.0: {} + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + json-schema-traverse@1.0.0: {} json-schema-typed@8.0.2: {} + json-stable-stringify-without-jsonify@1.0.1: {} + jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + locate-path@5.0.0: dependencies: p-locate: 4.1.0 + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + lodash.defaults@4.2.0: {} lodash.isarguments@3.1.0: {} + lodash.merge@4.6.2: {} + lodash.startcase@4.4.0: {} loupe@3.2.1: {} @@ -3137,12 +3851,22 @@ snapshots: dependencies: mime-db: 1.54.0 + minimatch@10.2.4: + dependencies: + brace-expansion: 5.0.4 + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.12 + mri@1.2.0: {} ms@2.1.3: {} nanoid@3.3.11: {} + natural-compare@1.4.0: {} + negotiator@1.0.0: {} object-assign@4.1.1: {} @@ -3157,6 +3881,15 @@ snapshots: dependencies: wrappy: 1.0.2 + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + outdent@0.5.0: {} p-filter@2.1.0: @@ -3167,10 +3900,18 @@ snapshots: dependencies: p-try: 2.2.0 + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + p-locate@4.1.0: dependencies: p-limit: 2.3.0 + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + p-map@2.1.0: {} p-try@2.2.0: {} @@ -3179,6 +3920,10 @@ snapshots: dependencies: quansync: 0.2.11 + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + parseurl@1.3.3: {} path-exists@4.0.0: {} @@ -3211,6 +3956,8 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + prelude-ls@1.2.1: {} + prettier@2.8.8: {} prettier@3.8.1: {} @@ -3220,6 +3967,8 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 + punycode@2.3.1: {} + qs@6.15.0: dependencies: side-channel: 1.1.0 @@ -3256,6 +4005,8 @@ snapshots: require-from-string@2.0.2: {} + resolve-from@4.0.0: {} + resolve-from@5.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -3441,10 +4192,16 @@ snapshots: strip-bom@3.0.0: {} + strip-json-comments@3.1.1: {} + strip-literal@3.1.0: dependencies: js-tokens: 9.0.1 + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + term-size@2.2.1: {} tinybench@2.9.0: {} @@ -3474,6 +4231,10 @@ snapshots: toidentifier@1.0.1: {} + ts-api-utils@2.4.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + tsdown@0.12.9(typescript@5.9.3): dependencies: ansis: 4.2.0 @@ -3534,6 +4295,10 @@ snapshots: turbo-windows-64: 2.8.14 turbo-windows-arm64: 2.8.14 + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + type-is@2.0.1: dependencies: content-type: 1.0.5 @@ -3561,6 +4326,10 @@ snapshots: unpipe@1.0.0: {} + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + vary@1.1.2: {} vite-node@2.1.9(@types/node@22.19.15): @@ -3711,10 +4480,14 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + word-wrap@1.2.5: {} + wrappy@1.0.2: {} yaml@2.8.2: {} + yocto-queue@0.1.0: {} + zod-to-json-schema@3.25.1(zod@4.3.6): dependencies: zod: 4.3.6