From 305bf2a422a3d73494066307a545f49706b697a2 Mon Sep 17 00:00:00 2001 From: cadamsdev Date: Sun, 11 Jan 2026 00:44:20 -0500 Subject: [PATCH 1/7] added version command --- src/index.ts | 292 ++++++++++++++++++++++++++----------------------- src/version.ts | 149 +++++++++++++++++++++++++ 2 files changed, 307 insertions(+), 134 deletions(-) create mode 100644 src/version.ts diff --git a/src/index.ts b/src/index.ts index a34bfda..b28ada4 100755 --- a/src/index.ts +++ b/src/index.ts @@ -15,6 +15,7 @@ import path from 'path'; import { humanId } from 'human-id'; import pc from 'picocolors'; import { ChangesetConfig, readConfig } from './config.js'; +import { version } from './version.js'; async function findPackages(config: ChangesetConfig): Promise> { const packageJsonPaths = globSync({ @@ -73,6 +74,132 @@ async function getSelectedPackages( return selectedPackages; } +async function createChangeset(args: { empty?: boolean }) { + const config = readConfig(); + + if (args.empty) { + const changesetDir = path.join(process.cwd(), '.changeset'); + + if (!existsSync(changesetDir)) { + mkdirSync(changesetDir); + } + + const changesetID = humanId({ + separator: '-', + capitalize: false, + }); + + const changesetFileName = `${changesetID}.md`; + const changesetFilePath = path.join(changesetDir, changesetFileName); + const markdownContent = '---\n---\n\n'; + writeFileSync(changesetFilePath, markdownContent, { + encoding: 'utf-8', + }); + + console.log( + pc.green('Empty Changeset added! - you can now commit it\n') + ); + console.log( + pc.green( + 'If you want to modify or expand on the changeset summary, you can find it here' + ) + ); + console.log(pc.cyan('info'), pc.blue(changesetFilePath)); + return; + } + + const packages = await findPackages(config); + + if (packages.size === 0) { + console.log('No packages found.'); + return; + } + + const selectedPackages = await getSelectedPackages(packages); + if (selectedPackages.length === 0) { + console.log('No packages selected.'); + return; + } + + const sortedTypeKeys = Object.keys(config.lazyChangesets.types).sort((a, b) => { + return config.lazyChangesets.types[a].sort - config.lazyChangesets.types[b].sort; + }); + + const msgType = await select({ + message: 'Select changelog type', + options: sortedTypeKeys.map(key => { + const type = config.lazyChangesets.types[key]; + return { + value: key, + label: `${type.emoji} ${key}`, + hint: type.displayName, + }; + }), + }); + + if (isCancel(msgType)) { + cancel('Operation cancelled.'); + process.exit(0); + } + + const changesetType = config.lazyChangesets.types[msgType]; + let isBreakingChange = false; + + if (changesetType.promptBreakingChange) { + const tempIsBreakingChange = await confirm({ + message: 'Is this a breaking change?', + initialValue: false, + }); + + if (isCancel(tempIsBreakingChange)) { + cancel('Operation cancelled.'); + process.exit(0); + } + + isBreakingChange = tempIsBreakingChange; + } + + const msg = await text({ + message: 'Enter a message for the changeset', + placeholder: 'e.g Added x feature', + validate(value) { + if (value.length === 0) return 'Message cannot be empty.'; + }, + }); + + if (isCancel(msg)) { + cancel('Operation cancelled.'); + process.exit(0); + } + + const changesetDir = path.join(process.cwd(), '.changeset'); + + if (!existsSync(changesetDir)) { + mkdirSync(changesetDir); + } + + const changesetID = humanId({ + separator: '-', + capitalize: false, + }); + + const changesetFileName = `${changesetID}.md`; + const changesetFilePath = path.join(changesetDir, changesetFileName); + let changesetContent = '---\n'; + selectedPackages.forEach((pkg) => { + changesetContent += `"${pkg}": ${msgType.toString()}${ + isBreakingChange ? '!' : '' + }\n`; + }); + + changesetContent += '---\n\n'; + changesetContent += `${msg.toString()}\n`; + + writeFileSync(changesetFilePath, changesetContent, { + encoding: 'utf-8', + }); +} + (async () => { try { const main = defineCommand({ @@ -80,12 +207,38 @@ async function getSelectedPackages( name: 'lazy-changesets', description: 'A CLI tool for generating changesets.', }, - args: { + subCommands: { init: { - type: 'positional', - description: 'Initialize changesets', - required: false, + meta: { + name: 'init', + description: 'Initialize changesets', + }, + args: {}, + run: async () => { + await init(); + process.exit(0); + }, + }, + version: { + meta: { + name: 'version', + description: 'Bump package versions based on changesets', + }, + args: { + 'dry-run': { + type: 'boolean', + description: 'Show what would be changed without modifying files', + required: false, + default: false, + }, + }, + run: async ({ args }) => { + await version({ dryRun: args['dry-run'] }); + process.exit(0); + }, }, + }, + args: { empty: { type: 'boolean', description: 'Create an empty changeset', @@ -94,136 +247,7 @@ async function getSelectedPackages( }, }, run: async ({ args }) => { - if (args.init) { - await init(); - return; - } - - const config = readConfig(); - - if (args.empty) { - const changesetDir = path.join(process.cwd(), '.changeset'); - - if (!existsSync(changesetDir)) { - mkdirSync(changesetDir); - } - - const changesetID = humanId({ - separator: '-', - capitalize: false, - }); - - const changesetFileName = `${changesetID}.md`; - const changesetFilePath = path.join(changesetDir, changesetFileName); - const markdownContent = '---\n---\n\n'; - writeFileSync(changesetFilePath, markdownContent, { - encoding: 'utf-8', - }); - - console.log( - pc.green('Empty Changeset added! - you can now commit it\n') - ); - console.log( - pc.green( - 'If you want to modify or expand on the changeset summary, you can find it here' - ) - ); - console.log(pc.cyan('info'), pc.blue(changesetFilePath)); - return; - } - - const packages = await findPackages(config); - - if (packages.size === 0) { - console.log('No packages found.'); - return; - } - - const selectedPackages = await getSelectedPackages(packages); - if (selectedPackages.length === 0) { - console.log('No packages selected.'); - return; - } - - const sortedTypeKeys = Object.keys(config.lazyChangesets.types).sort((a, b) => { - return config.lazyChangesets.types[a].sort - config.lazyChangesets.types[b].sort; - }); - - const msgType = await select({ - message: 'Select changelog type', - options: sortedTypeKeys.map(key => { - const type = config.lazyChangesets.types[key]; - return { - value: key, - label: `${type.emoji} ${key}`, - hint: type.displayName, - }; - }), - }); - - if (isCancel(msgType)) { - cancel('Operation cancelled.'); - process.exit(0); - } - - const changesetType = config.lazyChangesets.types[msgType]; - let isBreakingChange = false; - - if (changesetType.promptBreakingChange) { - const tempIsBreakingChange = await confirm({ - message: 'Is this a breaking change?', - initialValue: false, - }); - - if (isCancel(tempIsBreakingChange)) { - cancel('Operation cancelled.'); - process.exit(0); - } - - isBreakingChange = tempIsBreakingChange; - } - - const msg = await text({ - message: 'Enter a message for the changeset', - placeholder: 'e.g Added x feature', - validate(value) { - if (value.length === 0) return 'Message cannot be empty.'; - }, - }); - - if (isCancel(msg)) { - cancel('Operation cancelled.'); - process.exit(0); - } - - const changesetDir = path.join(process.cwd(), '.changeset'); - - // create the changeset directory if it doesn't exist - if (!existsSync(changesetDir)) { - mkdirSync(changesetDir); - } - - const changesetID = humanId({ - separator: '-', - capitalize: false, - }); - - const changesetFileName = `${changesetID}.md`; - const changesetFilePath = path.join(changesetDir, changesetFileName); - let changesetContent = '---\n'; - selectedPackages.forEach((pkg) => { - changesetContent += `"${pkg}": ${msgType.toString()}${ - isBreakingChange ? '!' : '' - }\n`; - }); - - changesetContent += '---\n\n'; - changesetContent += `${msg.toString()}\n`; - - // write the changelog to the changeset file - writeFileSync(changesetFilePath, changesetContent, { - encoding: 'utf-8', - }); + await createChangeset({ empty: args.empty }); }, }); diff --git a/src/version.ts b/src/version.ts new file mode 100644 index 0000000..d2605fc --- /dev/null +++ b/src/version.ts @@ -0,0 +1,149 @@ +import { readFileSync, writeFileSync, existsSync, unlinkSync } from 'fs'; +import { globSync } from 'tinyglobby'; +import path from 'path'; +import pc from 'picocolors'; +import { readConfig } from './config.js'; + +interface ChangesetReleaseType { + type: 'major' | 'minor' | 'patch'; + packageName: string; +} + +function parseChangesetFile(filePath: string): ChangesetReleaseType[] { + const content = readFileSync(filePath, 'utf-8'); + const releases: ChangesetReleaseType[] = []; + + const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/); + if (!frontmatterMatch) { + return releases; + } + + const frontmatter = frontmatterMatch[1]; + const lines = frontmatter.split('\n'); + + for (const line of lines) { + const match = line.match(/^"([^"]+)":\s*(\w+)(!?)/); + if (match) { + const packageName = match[1]; + const changesetType = match[2]; + const isBreaking = match[3] === '!'; + + let releaseType: ChangesetReleaseType['type'] = 'patch'; + + if (isBreaking) { + releaseType = 'major'; + } else if (changesetType === 'feat') { + releaseType = 'minor'; + } + + releases.push({ type: releaseType, packageName }); + } + } + + return releases; +} + +function getHighestReleaseType(releases: ChangesetReleaseType[]): ChangesetReleaseType['type'] { + if (releases.some(r => r.type === 'major')) return 'major'; + if (releases.some(r => r.type === 'minor')) return 'minor'; + return 'patch'; +} + +function bumpVersion(version: string, releaseType: ChangesetReleaseType['type']): string { + const parts = version.split('.').map(Number); + + if (parts.length !== 3 || parts.some(isNaN)) { + throw new Error(`Invalid version format: ${version}`); + } + + switch (releaseType) { + case 'major': + return `${parts[0] + 1}.0.0`; + case 'minor': + return `${parts[0]}.${parts[1] + 1}.0`; + case 'patch': + return `${parts[0]}.${parts[1]}.${parts[2] + 1}`; + } +} + +export async function version({ dryRun = false, ignore = [] as string[] } = {}) { + const config = readConfig(); + const changesetDir = path.join(process.cwd(), '.changeset'); + + if (!existsSync(changesetDir)) { + console.error(pc.red('No .changeset directory found.')); + process.exit(1); + } + + const changesetFiles = globSync({ + patterns: ['.changeset/*.md'], + ignore: ['.changeset/README.md', ...ignore.map(i => `.changeset/${i}`)], + }); + + if (changesetFiles.length === 0) { + console.log(pc.yellow('No changeset files found.')); + return; + } + + const packageReleases: Map = new Map(); + + for (const changesetFile of changesetFiles) { + const releases = parseChangesetFile(changesetFile); + for (const release of releases) { + const existing = packageReleases.get(release.packageName) || []; + packageReleases.set(release.packageName, [...existing, release]); + } + } + + if (packageReleases.size === 0) { + console.log(pc.yellow('No package releases found in changeset files.')); + return; + } + + const packageJsonPaths = globSync({ + patterns: ['**/package.json', '!**/node_modules/**', '!**/dist/**'], + }); + + const updatedPackages: string[] = []; + + for (const packageJsonPath of packageJsonPaths) { + const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')); + const packageName = packageJson.name; + + if (!packageName) continue; + + const releases = packageReleases.get(packageName); + if (!releases) continue; + + const currentVersion = packageJson.version; + const highestReleaseType = getHighestReleaseType(releases); + const newVersion = bumpVersion(currentVersion, highestReleaseType); + + packageJson.version = newVersion; + + if (!dryRun) { + writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf-8'); + } + + console.log( + pc.green('✔'), + pc.cyan(packageName), + pc.dim(`(${currentVersion} → ${newVersion})`) + ); + + updatedPackages.push(packageName); + } + + if (dryRun) { + console.log(pc.yellow('\nDry run - no files were modified.')); + } else { + console.log(pc.green(`\nUpdated ${updatedPackages.length} package(s).`)); + + for (const changesetFile of changesetFiles) { + unlinkSync(changesetFile); + console.log(pc.dim(`Deleted ${changesetFile}`)); + } + + console.log(pc.green(`\nDeleted ${changesetFiles.length} changeset file(s).`)); + } +} From 116932f1e8a58cca997aef7d488b009b0092a8e9 Mon Sep 17 00:00:00 2001 From: cadamsdev Date: Sun, 11 Jan 2026 00:48:23 -0500 Subject: [PATCH 2/7] added unit test agent --- .opencode/agent/unit-test-generator.md | 58 ++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .opencode/agent/unit-test-generator.md diff --git a/.opencode/agent/unit-test-generator.md b/.opencode/agent/unit-test-generator.md new file mode 100644 index 0000000..277b27d --- /dev/null +++ b/.opencode/agent/unit-test-generator.md @@ -0,0 +1,58 @@ +--- +description: >- + Use this agent when you need to create comprehensive unit tests for code, + functions, or modules. Examples: Context: User has just written a new + function and wants to ensure it's properly tested. user: 'I just wrote this + function to calculate the factorial of a number. Can you help me test it?' + assistant: 'I'll use the unit-test-generator agent to create comprehensive + unit tests for your factorial function.' Since the user needs unit + tests for their code, use the unit-test-generator agent to create appropriate + test cases. Context: User is working on a + class and wants to verify all its methods work correctly. user: 'Here's my + UserAccount class with methods for login, logout, and password reset. I need + tests for all of these.' assistant: 'Let me use the unit-test-generator agent + to create thorough unit tests for your UserAccount class.' The + user needs unit tests for multiple methods in a class, which is exactly what + the unit-test-generator agent is designed for. +mode: subagent +--- +You are a Unit Test Generator, an expert software testing specialist with deep knowledge of testing frameworks, test design patterns, and quality assurance methodologies. Your primary responsibility is creating comprehensive, maintainable, and effective unit tests that ensure code reliability and catch potential bugs. + +When generating unit tests, you will: + +1. **Analyze the Code Thoroughly**: Examine the function, method, or class to understand its purpose, parameters, return values, edge cases, and potential failure points. Identify all possible execution paths. + +2. **Choose Appropriate Testing Framework**: Select the most suitable testing framework based on the language and project context (e.g., Jest for JavaScript, pytest for Python, JUnit for Java, etc.). If the user hasn't specified a preference, ask for clarification or use the most common framework for that language. + +3. **Design Comprehensive Test Cases**: Create tests that cover: + - Normal/expected cases with typical inputs + - Edge cases (boundary values, empty inputs, null/undefined values) + - Error conditions and exception handling + - Performance considerations if relevant + - Integration points with dependencies + +4. **Structure Tests Properly**: Follow testing best practices including: + - Clear, descriptive test names that explain what is being tested + - Arrange-Act-Assert (Given-When-Then) pattern + - Independent test cases that don't rely on each other + - Proper setup and teardown when needed + - Mocking/stubbing external dependencies appropriately + +5. **Include Assertions**: Write meaningful assertions that verify: + - Expected return values + - State changes + - Exception throwing + - Method calls on mocks + - Side effects + +6. **Add Documentation**: Include comments explaining complex test logic and the rationale behind specific test cases. + +7. **Ensure Test Quality**: Verify that tests are: + - Deterministic (produce same results on repeated runs) + - Fast and efficient + - Isolated from external systems + - Easy to understand and maintain + +8. **Provide Usage Instructions**: Include information on how to run the tests and any setup requirements. + +If the code provided is incomplete or you need clarification about expected behavior, ask specific questions to ensure the tests will be accurate and comprehensive. Always prioritize test coverage and reliability over quantity of tests. From 28321dc44cfa4dc8fa1a440a3f51b9225db9cd24 Mon Sep 17 00:00:00 2001 From: cadamsdev Date: Sun, 11 Jan 2026 00:48:53 -0500 Subject: [PATCH 3/7] wip --- .opencode/agent/unit-test-generator.md | 58 -------------------------- 1 file changed, 58 deletions(-) delete mode 100644 .opencode/agent/unit-test-generator.md diff --git a/.opencode/agent/unit-test-generator.md b/.opencode/agent/unit-test-generator.md deleted file mode 100644 index 277b27d..0000000 --- a/.opencode/agent/unit-test-generator.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -description: >- - Use this agent when you need to create comprehensive unit tests for code, - functions, or modules. Examples: Context: User has just written a new - function and wants to ensure it's properly tested. user: 'I just wrote this - function to calculate the factorial of a number. Can you help me test it?' - assistant: 'I'll use the unit-test-generator agent to create comprehensive - unit tests for your factorial function.' Since the user needs unit - tests for their code, use the unit-test-generator agent to create appropriate - test cases. Context: User is working on a - class and wants to verify all its methods work correctly. user: 'Here's my - UserAccount class with methods for login, logout, and password reset. I need - tests for all of these.' assistant: 'Let me use the unit-test-generator agent - to create thorough unit tests for your UserAccount class.' The - user needs unit tests for multiple methods in a class, which is exactly what - the unit-test-generator agent is designed for. -mode: subagent ---- -You are a Unit Test Generator, an expert software testing specialist with deep knowledge of testing frameworks, test design patterns, and quality assurance methodologies. Your primary responsibility is creating comprehensive, maintainable, and effective unit tests that ensure code reliability and catch potential bugs. - -When generating unit tests, you will: - -1. **Analyze the Code Thoroughly**: Examine the function, method, or class to understand its purpose, parameters, return values, edge cases, and potential failure points. Identify all possible execution paths. - -2. **Choose Appropriate Testing Framework**: Select the most suitable testing framework based on the language and project context (e.g., Jest for JavaScript, pytest for Python, JUnit for Java, etc.). If the user hasn't specified a preference, ask for clarification or use the most common framework for that language. - -3. **Design Comprehensive Test Cases**: Create tests that cover: - - Normal/expected cases with typical inputs - - Edge cases (boundary values, empty inputs, null/undefined values) - - Error conditions and exception handling - - Performance considerations if relevant - - Integration points with dependencies - -4. **Structure Tests Properly**: Follow testing best practices including: - - Clear, descriptive test names that explain what is being tested - - Arrange-Act-Assert (Given-When-Then) pattern - - Independent test cases that don't rely on each other - - Proper setup and teardown when needed - - Mocking/stubbing external dependencies appropriately - -5. **Include Assertions**: Write meaningful assertions that verify: - - Expected return values - - State changes - - Exception throwing - - Method calls on mocks - - Side effects - -6. **Add Documentation**: Include comments explaining complex test logic and the rationale behind specific test cases. - -7. **Ensure Test Quality**: Verify that tests are: - - Deterministic (produce same results on repeated runs) - - Fast and efficient - - Isolated from external systems - - Easy to understand and maintain - -8. **Provide Usage Instructions**: Include information on how to run the tests and any setup requirements. - -If the code provided is incomplete or you need clarification about expected behavior, ask specific questions to ensure the tests will be accurate and comprehensive. Always prioritize test coverage and reliability over quantity of tests. From 4958ff6429d26a79ad43b83e286af1602f372c06 Mon Sep 17 00:00:00 2001 From: cadamsdev Date: Sun, 11 Jan 2026 01:02:42 -0500 Subject: [PATCH 4/7] setup vitest unit tests --- package-lock.json | 1425 ++++++++++++++++++++++++++++++++++++++++++- package.json | 6 +- src/version.test.ts | 549 +++++++++++++++++ src/version.ts | 8 +- vitest.config.ts | 9 + 5 files changed, 1979 insertions(+), 18 deletions(-) create mode 100644 src/version.test.ts create mode 100644 vitest.config.ts diff --git a/package-lock.json b/package-lock.json index 0b1ed27..1a16e46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,8 @@ }, "devDependencies": { "@types/node": "^22.15.15", - "typescript": "^5.8.3" + "typescript": "^5.8.3", + "vitest": "^4.0.16" } }, "node_modules/@clack/core": { @@ -44,16 +45,979 @@ "sisteransi": "^1.0.5" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", + "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", + "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", + "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", + "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", + "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", + "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", + "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", + "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", + "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", + "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", + "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", + "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", + "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", + "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", + "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", + "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", + "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", + "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", + "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", + "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", + "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", + "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", + "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", + "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", + "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "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/node": { "version": "22.15.15", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.15.tgz", "integrity": "sha512-R5muMcZob3/Jjchn5LcO8jdKwSCbzqmPB6ruBxMcf9kbxtniZHP327s6C37iOfuw8mbKK3cAQa7sEl7afLrQ8A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } }, + "node_modules/@vitest/expect": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.16.tgz", + "integrity": "sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.16", + "@vitest/utils": "4.0.16", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.16.tgz", + "integrity": "sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.16", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.16.tgz", + "integrity": "sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.16.tgz", + "integrity": "sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.16", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.16.tgz", + "integrity": "sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.16", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.16.tgz", + "integrity": "sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.16.tgz", + "integrity": "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.16", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/citty": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", @@ -72,11 +1036,83 @@ "node": "^14.18.0 || >=16.10.0" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "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/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -86,6 +1122,21 @@ } } }, + "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/human-id": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/human-id/-/human-id-4.1.1.tgz", @@ -95,6 +1146,53 @@ "human-id": "dist/cli.js" } }, + "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/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "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", @@ -102,9 +1200,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", "peer": true, "engines": { @@ -114,20 +1212,142 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", + "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.55.1", + "@rollup/rollup-android-arm64": "4.55.1", + "@rollup/rollup-darwin-arm64": "4.55.1", + "@rollup/rollup-darwin-x64": "4.55.1", + "@rollup/rollup-freebsd-arm64": "4.55.1", + "@rollup/rollup-freebsd-x64": "4.55.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", + "@rollup/rollup-linux-arm-musleabihf": "4.55.1", + "@rollup/rollup-linux-arm64-gnu": "4.55.1", + "@rollup/rollup-linux-arm64-musl": "4.55.1", + "@rollup/rollup-linux-loong64-gnu": "4.55.1", + "@rollup/rollup-linux-loong64-musl": "4.55.1", + "@rollup/rollup-linux-ppc64-gnu": "4.55.1", + "@rollup/rollup-linux-ppc64-musl": "4.55.1", + "@rollup/rollup-linux-riscv64-gnu": "4.55.1", + "@rollup/rollup-linux-riscv64-musl": "4.55.1", + "@rollup/rollup-linux-s390x-gnu": "4.55.1", + "@rollup/rollup-linux-x64-gnu": "4.55.1", + "@rollup/rollup-linux-x64-musl": "4.55.1", + "@rollup/rollup-openbsd-x64": "4.55.1", + "@rollup/rollup-openharmony-arm64": "4.55.1", + "@rollup/rollup-win32-arm64-msvc": "4.55.1", + "@rollup/rollup-win32-ia32-msvc": "4.55.1", + "@rollup/rollup-win32-x64-gnu": "4.55.1", + "@rollup/rollup-win32-x64-msvc": "4.55.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "license": "MIT" }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", - "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "license": "MIT", "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">=12.0.0" @@ -136,6 +1356,16 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -156,6 +1386,177 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.16.tgz", + "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.16", + "@vitest/mocker": "4.0.16", + "@vitest/pretty-format": "4.0.16", + "@vitest/runner": "4.0.16", + "@vitest/snapshot": "4.0.16", + "@vitest/spy": "4.0.16", + "@vitest/utils": "4.0.16", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.16", + "@vitest/browser-preview": "4.0.16", + "@vitest/browser-webdriverio": "4.0.16", + "@vitest/ui": "4.0.16", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } } } } diff --git a/package.json b/package.json index a3c60d1..95cc89e 100644 --- a/package.json +++ b/package.json @@ -8,14 +8,16 @@ }, "scripts": { "start": "npx tsx src/index.ts", - "build": "tsc -p tsconfig.build.json" + "build": "tsc -p tsconfig.build.json", + "test": "vitest" }, "author": "", "license": "ISC", "description": "", "devDependencies": { "@types/node": "^22.15.15", - "typescript": "^5.8.3" + "typescript": "^5.8.3", + "vitest": "^4.0.16" }, "dependencies": { "@clack/prompts": "^0.10.1", diff --git a/src/version.test.ts b/src/version.test.ts new file mode 100644 index 0000000..3047568 --- /dev/null +++ b/src/version.test.ts @@ -0,0 +1,549 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { readFileSync, writeFileSync, existsSync, unlinkSync, mkdirSync } from 'fs'; +import path from 'path'; +import { globSync } from 'tinyglobby'; +import { readConfig } from './config.js'; +import { + parseChangesetFile, + getHighestReleaseType, + bumpVersion, + version, + type ChangesetReleaseType +} from './version.js'; + +vi.mock('fs'); +vi.mock('tinyglobby'); +vi.mock('./config.js', () => ({ + readConfig: vi.fn(() => ({ + access: 'restricted', + baseBranch: 'main', + updateInternalDependencies: 'patch', + ignore: [], + lazyChangesets: { + types: { + feat: { + displayName: 'New Features', + emoji: '🚀', + sort: 0, + releaseType: 'minor', + promptBreakingChange: true, + }, + }, + }, + })), +})); + +describe('parseChangesetFile', () => { + it('should parse a simple changeset file with feat type', () => { + const content = `--- +"@test/package": feat +--- + +Added new feature`; + + vi.mocked(readFileSync).mockReturnValue(content); + vi.mocked(existsSync).mockReturnValue(true); + + const result = parseChangesetFile('.changeset/test.md'); + + expect(result).toEqual([ + { type: 'minor', packageName: '@test/package' } + ]); + }); + + it('should parse a changeset file with breaking change', () => { + const content = `--- +"@test/package": feat! +--- + +Breaking change added`; + + vi.mocked(readFileSync).mockReturnValue(content); + vi.mocked(existsSync).mockReturnValue(true); + + const result = parseChangesetFile('.changeset/test.md'); + + expect(result).toEqual([ + { type: 'major', packageName: '@test/package' } + ]); + }); + + it('should parse a changeset file with fix type', () => { + const content = `--- +"@test/package": fix +--- + +Bug fix`; + + vi.mocked(readFileSync).mockReturnValue(content); + vi.mocked(existsSync).mockReturnValue(true); + + const result = parseChangesetFile('.changeset/test.md'); + + expect(result).toEqual([ + { type: 'patch', packageName: '@test/package' } + ]); + }); + + it('should parse a changeset file with multiple packages', () => { + const content = `--- +"@test/package": feat +"@other/package": fix +--- + +Multiple packages updated`; + + vi.mocked(readFileSync).mockReturnValue(content); + vi.mocked(existsSync).mockReturnValue(true); + + const result = parseChangesetFile('.changeset/test.md'); + + expect(result).toEqual([ + { type: 'minor', packageName: '@test/package' }, + { type: 'patch', packageName: '@other/package' } + ]); + }); + + it('should parse a changeset file with malformed lines', () => { + const content = `--- +invalid line +"@test/package": feat +another invalid line +--- + +Test`; + + vi.mocked(readFileSync).mockReturnValue(content); + vi.mocked(existsSync).mockReturnValue(true); + + const result = parseChangesetFile('.changeset/test.md'); + + expect(result).toEqual([ + { type: 'minor', packageName: '@test/package' } + ]); + }); + + it('should parse a changeset file with multiple breaking changes', () => { + const content = `--- +"@test/package": feat! +"@other/package": fix! +--- + +Multiple breaking changes`; + + vi.mocked(readFileSync).mockReturnValue(content); + vi.mocked(existsSync).mockReturnValue(true); + + const result = parseChangesetFile('.changeset/test.md'); + + expect(result).toEqual([ + { type: 'major', packageName: '@test/package' }, + { type: 'major', packageName: '@other/package' } + ]); + }); + + it('should return empty array for changeset without frontmatter', () => { + const content = `No frontmatter here`; + + vi.mocked(readFileSync).mockReturnValue(content); + vi.mocked(existsSync).mockReturnValue(true); + + const result = parseChangesetFile('.changeset/test.md'); + + expect(result).toEqual([]); + }); + + it('should return empty array for changeset with empty frontmatter', () => { + const content = `--- +--- +`; + + vi.mocked(readFileSync).mockReturnValue(content); + vi.mocked(existsSync).mockReturnValue(true); + + const result = parseChangesetFile('.changeset/test.md'); + + expect(result).toEqual([]); + }); +}); + +describe('getHighestReleaseType', () => { + it('should return major when any release is major', () => { + const releases: ChangesetReleaseType[] = [ + { type: 'major', packageName: '@test/package' }, + { type: 'patch', packageName: '@test/package' } + ]; + + expect(getHighestReleaseType(releases)).toBe('major'); + }); + + it('should return minor when no major but has minor', () => { + const releases: ChangesetReleaseType[] = [ + { type: 'minor', packageName: '@test/package' }, + { type: 'patch', packageName: '@test/package' } + ]; + + expect(getHighestReleaseType(releases)).toBe('minor'); + }); + + it('should return patch when only patches', () => { + const releases: ChangesetReleaseType[] = [ + { type: 'patch', packageName: '@test/package' }, + { type: 'patch', packageName: '@test/package' } + ]; + + expect(getHighestReleaseType(releases)).toBe('patch'); + }); + + it('should return patch for single patch', () => { + const releases: ChangesetReleaseType[] = [ + { type: 'patch', packageName: '@test/package' } + ]; + + expect(getHighestReleaseType(releases)).toBe('patch'); + }); + + it('should return major for single major', () => { + const releases: ChangesetReleaseType[] = [ + { type: 'major', packageName: '@test/package' } + ]; + + expect(getHighestReleaseType(releases)).toBe('major'); + }); + + it('should return minor for single minor', () => { + const releases: ChangesetReleaseType[] = [ + { type: 'minor', packageName: '@test/package' } + ]; + + expect(getHighestReleaseType(releases)).toBe('minor'); + }); +}); + +describe('bumpVersion', () => { + it('should bump major version correctly', () => { + expect(bumpVersion('1.0.0', 'major')).toBe('2.0.0'); + expect(bumpVersion('0.5.10', 'major')).toBe('1.0.0'); + }); + + it('should bump minor version correctly', () => { + expect(bumpVersion('1.0.0', 'minor')).toBe('1.1.0'); + expect(bumpVersion('1.5.10', 'minor')).toBe('1.6.0'); + }); + + it('should bump patch version correctly', () => { + expect(bumpVersion('1.0.0', 'patch')).toBe('1.0.1'); + expect(bumpVersion('1.5.10', 'patch')).toBe('1.5.11'); + }); + + it('should handle large version numbers', () => { + expect(bumpVersion('999.999.999', 'major')).toBe('1000.0.0'); + expect(bumpVersion('999.999.999', 'minor')).toBe('999.1000.0'); + expect(bumpVersion('999.999.999', 'patch')).toBe('999.999.1000'); + }); + + it('should throw error for invalid version format - missing parts', () => { + expect(() => bumpVersion('1.0', 'major')).toThrow('Invalid version format'); + }); + + it('should throw error for invalid version format - too many parts', () => { + expect(() => bumpVersion('1.0.0.0', 'major')).toThrow('Invalid version format'); + }); + + it('should throw error for invalid version format - non-numeric', () => { + expect(() => bumpVersion('a.b.c', 'major')).toThrow('Invalid version format'); + }); + + it('should throw error for invalid version format - mixed', () => { + expect(() => bumpVersion('1.b.0', 'major')).toThrow('Invalid version format'); + }); + + it('should handle zero versions', () => { + expect(bumpVersion('0.0.0', 'major')).toBe('1.0.0'); + expect(bumpVersion('0.0.0', 'minor')).toBe('0.1.0'); + expect(bumpVersion('0.0.0', 'patch')).toBe('0.0.1'); + }); +}); + +describe('version command', () => { + beforeEach(() => { + vi.clearAllMocks(); + vi.spyOn(console, 'log').mockImplementation(() => {}); + vi.spyOn(console, 'error').mockImplementation(() => {}); + vi.mocked(existsSync).mockImplementation((path: import('fs').PathLike) => { + const pathStr = typeof path === 'string' ? path : path.toString(); + if (pathStr.includes('.changeset')) return true; + return false; + }); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should exit with error when .changeset directory does not exist', async () => { + vi.mocked(existsSync).mockImplementation((path: import('fs').PathLike) => { + const pathStr = typeof path === 'string' ? path : path.toString(); + return !pathStr.includes('.changeset'); + }); + + const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => { + throw new Error('Process exited'); + }); + + try { + await version(); + } catch (e) { + expect((e as Error).message).toBe('Process exited'); + } + + expect(exitSpy).toHaveBeenCalledWith(1); + exitSpy.mockRestore(); + }); + + it('should log message when no changeset files found', async () => { + vi.mocked(globSync).mockReturnValue([]); + + await version(); + + expect(console.log).toHaveBeenCalledWith(expect.stringContaining('No changeset files found')); + }); + + it('should log message when no package releases found', async () => { + vi.mocked(readFileSync).mockReturnValue(`--- +--- +`); + vi.mocked(globSync).mockReturnValue(['.changeset/empty.md']); + + await version(); + + expect(console.log).toHaveBeenCalledWith(expect.stringContaining('No package releases found')); + }); + + it('should update package versions when changesets exist', async () => { + const changesetContent = `--- +"@test/package": feat +--- +New feature added`; + + const packageJsonContent = JSON.stringify({ + name: '@test/package', + version: '1.0.0', + }, null, 2); + + vi.mocked(readFileSync).mockImplementation((filePath) => { + const pathStr = typeof filePath === 'string' ? filePath : filePath.toString(); + if (pathStr.includes('.changeset')) { + return changesetContent; + } + return packageJsonContent; + }); + + vi.mocked(globSync).mockImplementation((options) => { + if (options?.patterns?.[0].includes('package.json')) { + return ['package.json']; + } + return ['.changeset/test.md']; + }); + + await version({ dryRun: true }); + + expect(console.log).toHaveBeenCalledWith( + expect.stringContaining('✔'), + expect.stringContaining('@test/package'), + expect.stringContaining('1.0.0 → 1.1.0') + ); + }); + + it('should delete changeset files after updating versions', async () => { + const changesetContent = `--- +"@test/package": patch +--- +Bug fix`; + + const packageJsonContent = JSON.stringify({ + name: '@test/package', + version: '1.0.0', + }, null, 2); + + vi.mocked(readFileSync).mockImplementation((filePath) => { + const pathStr = typeof filePath === 'string' ? filePath : filePath.toString(); + if (pathStr.includes('.changeset')) { + return changesetContent; + } + return packageJsonContent; + }); + + vi.mocked(globSync).mockImplementation((options) => { + if (options?.patterns?.[0].includes('package.json')) { + return ['package.json']; + } + return ['.changeset/test.md']; + }); + + await version({ dryRun: false }); + + expect(unlinkSync).toHaveBeenCalledWith('.changeset/test.md'); + }); + + it('should handle multiple packages in different changesets', async () => { + const changeset1Content = `--- +"@test/package": feat +--- +Feature for test package`; + + const changeset2Content = `--- +"@other/package": fix +--- +Fix for other package`; + + const packageJson1 = JSON.stringify({ + name: '@test/package', + version: '1.0.0', + }, null, 2); + + const packageJson2 = JSON.stringify({ + name: '@other/package', + version: '2.0.0', + }, null, 2); + + let readCallCount = 0; + vi.mocked(readFileSync).mockImplementation((filePath) => { + const pathStr = typeof filePath === 'string' ? filePath : filePath.toString(); + if (pathStr.includes('test.md')) { + return changeset1Content; + } else if (pathStr.includes('other.md')) { + return changeset2Content; + } else if (pathStr.includes('test/package.json')) { + return packageJson1; + } else { + return packageJson2; + } + }); + + vi.mocked(globSync).mockImplementation((options) => { + if (options?.patterns?.[0].includes('package.json')) { + return ['packages/test/package.json', 'packages/other/package.json']; + } + return ['.changeset/test.md', '.changeset/other.md']; + }); + + await version({ dryRun: true }); + + const logCalls = vi.mocked(console.log).mock.calls; + + expect(logCalls[0]).toEqual([ + expect.stringContaining('✔'), + expect.stringContaining('@test/package'), + expect.stringContaining('1.0.0 → 1.1.0') + ]); + expect(logCalls[1]).toEqual([ + expect.stringContaining('✔'), + expect.stringContaining('@other/package'), + expect.stringContaining('2.0.0 → 2.0.1') + ]); + }); + + it('should ignore specified changeset files', async () => { + const changesetContent = `--- +"@test/package": feat +--- +New feature added`; + + const packageJsonContent = JSON.stringify({ + name: '@test/package', + version: '1.0.0', + }, null, 2); + + vi.mocked(readFileSync).mockImplementation((filePath) => { + const pathStr = typeof filePath === 'string' ? filePath : filePath.toString(); + if (pathStr.includes('.changeset')) { + return changesetContent; + } + return packageJsonContent; + }); + + let globCallCount = 0; + vi.mocked(globSync).mockImplementation((options) => { + globCallCount++; + if (globCallCount === 1) { + return ['.changeset/test.md', '.changeset/ignored.md']; + } + if (options?.patterns?.[0].includes('package.json')) { + return ['package.json']; + } + return ['.changeset/test.md']; + }); + + await version({ dryRun: true, ignore: ['ignored.md'] }); + + const logCalls = vi.mocked(console.log).mock.calls; + + expect(logCalls[0]).toEqual([ + expect.stringContaining('✔'), + expect.stringContaining('@test/package'), + expect.stringContaining('1.0.0 → 1.1.0') + ]); + }); + + it('should handle package.json without name', async () => { + const changesetContent = `--- +"@test/package": feat +--- +New feature added`; + + const packageJsonContent = JSON.stringify({ + version: '1.0.0', + }, null, 2); + + vi.mocked(readFileSync).mockImplementation((filePath) => { + const pathStr = typeof filePath === 'string' ? filePath : filePath.toString(); + if (pathStr.includes('.changeset')) { + return changesetContent; + } + return packageJsonContent; + }); + + vi.mocked(globSync).mockImplementation((options) => { + if (options?.patterns?.[0].includes('package.json')) { + return ['package.json']; + } + return ['.changeset/test.md']; + }); + + await version({ dryRun: true }); + + expect(console.log).toHaveBeenCalledWith(expect.stringContaining('Dry run - no files were modified')); + }); + + it('should handle package.json without matching changeset', async () => { + const changesetContent = `--- +"@other/package": feat +--- +New feature added`; + + const packageJsonContent = JSON.stringify({ + name: '@test/package', + version: '1.0.0', + }, null, 2); + + vi.mocked(readFileSync).mockImplementation((filePath) => { + const pathStr = typeof filePath === 'string' ? filePath : filePath.toString(); + if (pathStr.includes('.changeset')) { + return changesetContent; + } + return packageJsonContent; + }); + + vi.mocked(globSync).mockImplementation((options) => { + if (options?.patterns?.[0].includes('package.json')) { + return ['package.json']; + } + return ['.changeset/test.md']; + }); + + await version({ dryRun: true }); + + expect(console.log).toHaveBeenCalledWith(expect.stringContaining('Dry run - no files were modified')); + }); +}); diff --git a/src/version.ts b/src/version.ts index d2605fc..75c2c76 100644 --- a/src/version.ts +++ b/src/version.ts @@ -4,12 +4,12 @@ import path from 'path'; import pc from 'picocolors'; import { readConfig } from './config.js'; -interface ChangesetReleaseType { +export interface ChangesetReleaseType { type: 'major' | 'minor' | 'patch'; packageName: string; } -function parseChangesetFile(filePath: string): ChangesetReleaseType[] { +export function parseChangesetFile(filePath: string): ChangesetReleaseType[] { const content = readFileSync(filePath, 'utf-8'); const releases: ChangesetReleaseType[] = []; @@ -43,13 +43,13 @@ function parseChangesetFile(filePath: string): ChangesetReleaseType[] { return releases; } -function getHighestReleaseType(releases: ChangesetReleaseType[]): ChangesetReleaseType['type'] { +export function getHighestReleaseType(releases: ChangesetReleaseType[]): ChangesetReleaseType['type'] { if (releases.some(r => r.type === 'major')) return 'major'; if (releases.some(r => r.type === 'minor')) return 'minor'; return 'patch'; } -function bumpVersion(version: string, releaseType: ChangesetReleaseType['type']): string { +export function bumpVersion(version: string, releaseType: ChangesetReleaseType['type']): string { const parts = version.split('.').map(Number); if (parts.length !== 3 || parts.some(isNaN)) { diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..edc9be9 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + exclude: ["dist"] + }, +}); From ce4d126017aa6aa53fe0eae67883f8c89035e49b Mon Sep 17 00:00:00 2001 From: cadamsdev Date: Sun, 11 Jan 2026 01:07:07 -0500 Subject: [PATCH 5/7] added changeset --- .changeset/thick-monkeys-tan.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/thick-monkeys-tan.md diff --git a/.changeset/thick-monkeys-tan.md b/.changeset/thick-monkeys-tan.md new file mode 100644 index 0000000..9ef93b6 --- /dev/null +++ b/.changeset/thick-monkeys-tan.md @@ -0,0 +1,5 @@ +--- +"@cadamsdev/lazy-changesets": feat +--- + +Added version command to bump the version in the package.json files based on the changesets From f55da7f6e1599788af0fabb5733d41a02c8bd96f Mon Sep 17 00:00:00 2001 From: cadamsdev Date: Sun, 11 Jan 2026 01:07:21 -0500 Subject: [PATCH 6/7] added AGENTS.md --- AGENTS.md | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..e900192 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,176 @@ +# Lazy Changesets - Agent Guidelines + +## Essential Commands + +### Build & Test +- `npm run build` - Compile TypeScript to dist/ using tsconfig.build.json +- `npm test` - Run all Vitest tests (watch mode) +- `npm test -- --run` - Run tests once +- `npm test -- --run src/version.test.ts` - Run single test file +- `npm test -- --run -t "test name"` - Run tests matching pattern +- `npm test:coverage` - Run tests with coverage report + +### Development +- `npm start` - Run the CLI directly using tsx + +## Code Style Guidelines + +### Imports +- Use ES module imports: `import { foo } from 'bar'` +- Use `.js` extensions for local imports: `import { foo } from './config.js'` +- Use `node:` prefix for Node.js built-ins: `import { readFileSync } from 'node:fs'` +- Group imports in order: built-ins, external deps, local deps +- Type imports should use `import type`: `import type { Foo } from './bar.js'` + +### TypeScript & Types +- Strict TypeScript enabled - always provide explicit types +- Use `interface` for object shapes, `type` for unions/primitives +- Export functions that need testing: `export function foo()` +- Use template literals for type indexing: `ChangesetReleaseType['type']` +- Default parameters: `async function foo({ dryRun = false } = {})` + +### Naming Conventions +- camelCase for functions, variables, and object properties +- PascalCase for types, interfaces, classes, and enum members +- UPPER_SNAKE_CASE for constants (rare - prefer descriptive names) +- File names: kebab-case for utilities, PascalCase for components (if any) +- Test files: `.test.ts` alongside source files + +### File Structure +- `src/*.ts` - Source code +- `src/*.test.ts` - Unit tests +- `dist/*.js` - Compiled output (generated) +- `.changeset/*.md` - Changeset files + +### Error Handling +- Throw `new Error()` with descriptive messages for programming errors +- Use `console.error()` with `pc.red()` for user-facing errors +- Use `console.warn()` for warnings +- Use `console.log()` with colored output (`pc.green()`, `pc.yellow()`) for success/info +- Exit with `process.exit(1)` on error, `process.exit(0)` on success +- Validate inputs early with guard clauses + +### CLI Pattern (citty) +- Use `defineCommand()` to define main CLI entry +- Define `subCommands` for nested commands (init, version, etc.) +- Define `args` for command options +- Use `runMain()` to execute command +- Call `process.exit(0)` explicitly in subCommands to prevent prompt rendering + +### Testing Patterns (Vitest) +- Mock external dependencies: `vi.mock('fs')`, `vi.mock('tinyglobby')` +- Use `vi.mocked()` for typed mocks: `vi.mocked(readFileSync).mockReturnValue()` +- Mock config: `vi.mock('./config.js', () => ({ readConfig: vi.fn(...) }))` +- Use `beforeEach`/`afterEach` for setup/teardown +- Spy on console: `vi.spyOn(console, 'log').mockImplementation(() => {})` +- Test grouping: `describe('functionName', () => { ... })` +- Test descriptions: `it('should do X when Y', () => { ... })` + +### File Operations +- Check existence first: `if (!existsSync(path)) { ... }` +- Use `path.join()` for path construction +- Write with encoding: `writeFileSync(path, content, { encoding: 'utf-8' })` +- JSON stringify: `JSON.stringify(obj, null, 2) + '\n'` + +### Console Output +- Use `picocolors` (`pc`) for colored terminal output +- Structure: `console.log(pc.green('✔'), pc.cyan('name'), pc.dim('(info)'))` +- Keep messages concise and user-friendly +- Use emoji sparingly and appropriately + +### General Principles +- No code comments (unless absolutely necessary) +- Keep functions small and focused +- Use Map/Set over Object when appropriate +- Process files in loops with explicit continue for error cases +- Use `Array.from()` to convert collections +- String interpolation with template literals +- Early returns over nested conditionals + +### Async/Await Patterns +- Always use `async`/`await` for async operations +- Return Promises from async functions +- Handle errors with try/catch in async functions +- Use `Promise.all()` for parallel async operations when safe + +### Map/Set Usage +- Prefer Map for key-value pairs with arbitrary keys +- Prefer Set for unique value collections +- Use `Array.from(map.values())` or `[...map.values()]` to convert to arrays +- Use `map.has()`, `map.get()`, `map.set()` methods +- Use `set.has()`, `set.add()`, `set.delete()` methods + +### String & Template Literals +- Use template literals for string interpolation: `\`Hello ${name}\`` +- Use template literals for multi-line strings +- Prefer string methods over regex for simple operations +- Use regex with `match()` for complex pattern matching + +### Array Operations +- Use array methods: `map()`, `filter()`, `find()`, `some()`, `every()` +- Use `for...of` loops when you need `break` or `continue` +- Use `Array.from()` to convert Map/Set/NodeList to arrays +- Use spread operator: `[...array]` for copying + +### Object & JSON +- Use interfaces for object shapes +- Use `JSON.stringify(obj, null, 2)` for pretty-printed JSON +- Use `JSON.parse()` with try/catch for parsing +- Destructure objects: `const { name, version } = packageJson` + +### Path Handling +- Always use `path.join()` for cross-platform paths +- Use `path.resolve()` for absolute paths +- Use `path.dirname()` to get directory path +- Use `path.basename()` to get filename + +### Package Management +- Use `npm install` to add dependencies +- Use `npm install --save-dev` for dev dependencies +- Check package.json for existing dependencies before adding new ones +- Update package.json version when making breaking changes + +### Git Workflow +- Create changeset files for all changes +- Run `npm run build` before committing +- Run `npm test -- --run` to verify tests pass +- Include changeset file in commits +- Delete consumed changeset files after version bump + +### CLI Argument Handling +- Use citty's `defineCommand()` for command structure +- Define args with type, description, required, default +- Access args via `({ args }) => { ... }` +- Use subCommands for nested CLI structure +- Call `process.exit(0)` to prevent interactive prompts after subCommands + +### Version Bumping Logic +- Parse changeset frontmatter for release types +- `feat` type = minor version bump +- `!` suffix = major version bump +- All other types = patch version bump +- Take highest bump when multiple changesets exist for same package +- Delete changeset files after successful version bump + +### File System Patterns +- Use `existsSync()` before file operations (when appropriate) +- Use `readFileSync()` with encoding: 'utf-8' +- Use `writeFileSync()` with encoding: 'utf-8' +- Use `mkdirSync()` for directory creation +- Use `unlinkSync()` for file deletion +- Use glob patterns for finding files + +### Color Console Output +- Import: `import pc from 'picocolors'` +- Success: `pc.green()`, `pc.cyan()`, `pc.green('✔')` +- Warning: `pc.yellow()` +- Error: `pc.red()` +- Dim/Info: `pc.dim()` +- Bold: `pc.bold()` + +### Type Safety +- Never use `any` type +- Use `unknown` when type is truly unknown +- Use type guards for narrowing types +- Use `as` sparingly and only when certain +- Use template literal types for dynamic keys: `T['key']` From 28834490907651a2900065244574fd116f4171ce Mon Sep 17 00:00:00 2001 From: cadamsdev Date: Sun, 11 Jan 2026 01:31:17 -0500 Subject: [PATCH 7/7] added --install flag --- package-lock.json | 7 ++ package.json | 1 + src/index.ts | 8 +- src/version.test.ts | 284 +++++++++++++++++++++++++++++++++++++++++--- src/version.ts | 43 ++++++- 5 files changed, 324 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1a16e46..aa309eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@clack/prompts": "^0.10.1", "citty": "^0.1.6", "human-id": "^4.1.1", + "package-manager-detector": "^1.6.0", "picocolors": "^1.1.1", "tinyglobby": "^0.2.13" }, @@ -1186,6 +1187,12 @@ ], "license": "MIT" }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "license": "MIT" + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", diff --git a/package.json b/package.json index 95cc89e..3dbfd1d 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@clack/prompts": "^0.10.1", "citty": "^0.1.6", "human-id": "^4.1.1", + "package-manager-detector": "^1.6.0", "picocolors": "^1.1.1", "tinyglobby": "^0.2.13" } diff --git a/src/index.ts b/src/index.ts index b28ada4..f7c8229 100755 --- a/src/index.ts +++ b/src/index.ts @@ -231,9 +231,15 @@ async function createChangeset(args: { empty?: boolean }) { required: false, default: false, }, + install: { + type: 'boolean', + description: 'Run package manager install after version bump', + required: false, + default: false, + }, }, run: async ({ args }) => { - await version({ dryRun: args['dry-run'] }); + await version({ dryRun: args['dry-run'], install: args.install }); process.exit(0); }, }, diff --git a/src/version.test.ts b/src/version.test.ts index 3047568..e504323 100644 --- a/src/version.test.ts +++ b/src/version.test.ts @@ -2,6 +2,8 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { readFileSync, writeFileSync, existsSync, unlinkSync, mkdirSync } from 'fs'; import path from 'path'; import { globSync } from 'tinyglobby'; +import { execSync } from 'node:child_process'; +import { detect } from 'package-manager-detector'; import { readConfig } from './config.js'; import { parseChangesetFile, @@ -13,24 +15,26 @@ import { vi.mock('fs'); vi.mock('tinyglobby'); +vi.mock('node:child_process'); +vi.mock('package-manager-detector'); vi.mock('./config.js', () => ({ - readConfig: vi.fn(() => ({ - access: 'restricted', - baseBranch: 'main', - updateInternalDependencies: 'patch', - ignore: [], - lazyChangesets: { - types: { - feat: { - displayName: 'New Features', - emoji: '🚀', - sort: 0, - releaseType: 'minor', - promptBreakingChange: true, - }, - }, - }, - })), + readConfig: vi.fn(() => ({ + access: 'restricted', + baseBranch: 'main', + updateInternalDependencies: 'patch', + ignore: [], + lazyChangesets: { + types: { + feat: { + displayName: 'New Features', + emoji: '🚀', + sort: 0, + releaseType: 'minor', + promptBreakingChange: true, + }, + }, + }, + })), })); describe('parseChangesetFile', () => { @@ -270,6 +274,7 @@ describe('version command', () => { vi.clearAllMocks(); vi.spyOn(console, 'log').mockImplementation(() => {}); vi.spyOn(console, 'error').mockImplementation(() => {}); + vi.spyOn(console, 'warn').mockImplementation(() => {}); vi.mocked(existsSync).mockImplementation((path: import('fs').PathLike) => { const pathStr = typeof path === 'string' ? path : path.toString(); if (pathStr.includes('.changeset')) return true; @@ -546,4 +551,249 @@ New feature added`; expect(console.log).toHaveBeenCalledWith(expect.stringContaining('Dry run - no files were modified')); }); + + describe('install flag', () => { + it('should run npm install when install flag is true', async () => { + const changesetContent = `--- +"@test/package": patch +--- +Bug fix`; + + const packageJsonContent = JSON.stringify({ + name: '@test/package', + version: '1.0.0', + }, null, 2); + + vi.mocked(readFileSync).mockImplementation((filePath) => { + const pathStr = typeof filePath === 'string' ? filePath : filePath.toString(); + if (pathStr.includes('.changeset')) { + return changesetContent; + } + return packageJsonContent; + }); + + vi.mocked(globSync).mockImplementation((options) => { + if (options?.patterns?.[0].includes('package.json')) { + return ['package.json']; + } + return ['.changeset/test.md']; + }); + + vi.mocked(execSync).mockReturnValue(''); + + vi.mocked(detect).mockResolvedValue({ name: 'npm', agent: 'npm' }); + + await version({ dryRun: false, install: true }); + + expect(execSync).toHaveBeenCalledWith('npm install', { stdio: 'inherit' }); + }); + + it('should run pnpm install when pnpm is detected', async () => { + const changesetContent = `--- +"@test/package": patch +--- +Bug fix`; + + const packageJsonContent = JSON.stringify({ + name: '@test/package', + version: '1.0.0', + }, null, 2); + + vi.mocked(readFileSync).mockImplementation((filePath) => { + const pathStr = typeof filePath === 'string' ? filePath : filePath.toString(); + if (pathStr.includes('.changeset')) { + return changesetContent; + } + return packageJsonContent; + }); + + vi.mocked(globSync).mockImplementation((options) => { + if (options?.patterns?.[0].includes('package.json')) { + return ['package.json']; + } + return ['.changeset/test.md']; + }); + + vi.mocked(execSync).mockReturnValue(''); + + vi.mocked(detect).mockResolvedValue({ name: 'pnpm', agent: 'pnpm' }); + + await version({ dryRun: false, install: true }); + + expect(execSync).toHaveBeenCalledWith('pnpm install', { stdio: 'inherit' }); + }); + + it('should run yarn install when yarn is detected', async () => { + const changesetContent = `--- +"@test/package": patch +--- +Bug fix`; + + const packageJsonContent = JSON.stringify({ + name: '@test/package', + version: '1.0.0', + }, null, 2); + + vi.mocked(readFileSync).mockImplementation((filePath) => { + const pathStr = typeof filePath === 'string' ? filePath : filePath.toString(); + if (pathStr.includes('.changeset')) { + return changesetContent; + } + return packageJsonContent; + }); + + vi.mocked(globSync).mockImplementation((options) => { + if (options?.patterns?.[0].includes('package.json')) { + return ['package.json']; + } + return ['.changeset/test.md']; + }); + + vi.mocked(execSync).mockReturnValue(''); + + vi.mocked(detect).mockResolvedValue({ name: 'yarn', agent: 'yarn' }); + + await version({ dryRun: false, install: true }); + + expect(execSync).toHaveBeenCalledWith('yarn install', { stdio: 'inherit' }); + }); + + it('should run bun install when bun is detected', async () => { + const changesetContent = `--- +"@test/package": patch +--- +Bug fix`; + + const packageJsonContent = JSON.stringify({ + name: '@test/package', + version: '1.0.0', + }, null, 2); + + vi.mocked(readFileSync).mockImplementation((filePath) => { + const pathStr = typeof filePath === 'string' ? filePath : filePath.toString(); + if (pathStr.includes('.changeset')) { + return changesetContent; + } + return packageJsonContent; + }); + + vi.mocked(globSync).mockImplementation((options) => { + if (options?.patterns?.[0].includes('package.json')) { + return ['package.json']; + } + return ['.changeset/test.md']; + }); + + vi.mocked(execSync).mockReturnValue(''); + + vi.mocked(detect).mockResolvedValue({ name: 'bun', agent: 'bun' }); + + await version({ dryRun: false, install: true }); + + expect(execSync).toHaveBeenCalledWith('bun install', { stdio: 'inherit' }); + }); + + it('should skip install when dryRun is true', async () => { + const changesetContent = `--- +"@test/package": patch +--- +Bug fix`; + + const packageJsonContent = JSON.stringify({ + name: '@test/package', + version: '1.0.0', + }, null, 2); + + vi.mocked(readFileSync).mockImplementation((filePath) => { + const pathStr = typeof filePath === 'string' ? filePath : filePath.toString(); + if (pathStr.includes('.changeset')) { + return changesetContent; + } + return packageJsonContent; + }); + + vi.mocked(globSync).mockImplementation((options) => { + if (options?.patterns?.[0].includes('package.json')) { + return ['package.json']; + } + return ['.changeset/test.md']; + }); + + vi.mocked(execSync).mockReturnValue(''); + + vi.mocked(detect).mockResolvedValue({ name: 'npm', agent: 'npm' }); + + await version({ dryRun: true, install: true }); + + expect(execSync).not.toHaveBeenCalled(); + }); + + it('should skip install when no packages are updated', async () => { + const changesetContent = `--- +"@test/package": patch +--- +Bug fix`; + + const packageJsonContent = JSON.stringify({ + name: '@other/package', + version: '1.0.0', + }, null, 2); + + vi.mocked(readFileSync).mockImplementation((filePath) => { + const pathStr = typeof filePath === 'string' ? filePath : filePath.toString(); + if (pathStr.includes('.changeset')) { + return changesetContent; + } + return packageJsonContent; + }); + + vi.mocked(globSync).mockImplementation((options) => { + if (options?.patterns?.[0].includes('package.json')) { + return ['package.json']; + } + return ['.changeset/test.md']; + }); + + vi.mocked(execSync).mockReturnValue(''); + + vi.mocked(detect).mockResolvedValue({ name: 'npm', agent: 'npm' }); + + await version({ dryRun: false, install: true }); + + expect(execSync).not.toHaveBeenCalled(); + }); + + it('should warn for unsupported package manager', async () => { + const changesetContent = `--- +"@test/package": patch +--- +Bug fix`; + + const packageJsonContent = JSON.stringify({ + name: '@test/package', + version: '1.0.0', + }, null, 2); + + vi.mocked(readFileSync).mockImplementation((filePath) => { + const pathStr = typeof filePath === 'string' ? filePath : filePath.toString(); + if (pathStr.includes('.changeset')) { + return changesetContent; + } + return packageJsonContent; + }); + + vi.mocked(globSync).mockImplementation((options) => { + if (options?.patterns?.[0].includes('package.json')) { + return ['package.json']; + } + return ['.changeset/test.md']; + }); + + vi.mocked(detect).mockResolvedValue({ name: 'deno', agent: 'deno' } as any); + + await version({ dryRun: false, install: true }); + + expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('Unsupported package manager')); + }); + }); }); diff --git a/src/version.ts b/src/version.ts index 75c2c76..6de08c6 100644 --- a/src/version.ts +++ b/src/version.ts @@ -2,6 +2,8 @@ import { readFileSync, writeFileSync, existsSync, unlinkSync } from 'fs'; import { globSync } from 'tinyglobby'; import path from 'path'; import pc from 'picocolors'; +import { execSync } from 'node:child_process'; +import { detect } from 'package-manager-detector'; import { readConfig } from './config.js'; export interface ChangesetReleaseType { @@ -66,7 +68,7 @@ export function bumpVersion(version: string, releaseType: ChangesetReleaseType[' } } -export async function version({ dryRun = false, ignore = [] as string[] } = {}) { +export async function version({ dryRun = false, ignore = [] as string[], install = false } = {}) { const config = readConfig(); const changesetDir = path.join(process.cwd(), '.changeset'); @@ -146,4 +148,43 @@ export async function version({ dryRun = false, ignore = [] as string[] } = {}) console.log(pc.green(`\nDeleted ${changesetFiles.length} changeset file(s).`)); } + + if (install && !dryRun && updatedPackages.length > 0) { + const detected = await detect(); + if (detected) { + const agent = detected.agent || detected.name; + let installCmd = ''; + + switch (agent) { + case 'npm': + installCmd = 'npm install'; + break; + case 'yarn': + case 'yarn@berry': + installCmd = 'yarn install'; + break; + case 'pnpm': + case 'pnpm@6': + installCmd = 'pnpm install'; + break; + case 'bun': + installCmd = 'bun install'; + break; + default: + console.warn(pc.yellow(`Unsupported package manager: ${agent}. Skipping install.`)); + return; + } + + console.log(`\n${pc.dim('Running')}`, pc.cyan(installCmd), pc.dim('...\n')); + try { + execSync(installCmd, { stdio: 'inherit' }); + console.log(pc.green('✔'), 'Install completed successfully'); + } catch (error) { + console.error(pc.red('✗'), 'Install failed'); + throw error; + } + } else { + console.warn(pc.yellow('Could not detect package manager. Skipping install.')); + } + } }