diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 686509c8..9bf68b61 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -26,7 +26,6 @@ "markdown.extension.list.indentationSize": "adaptive", "markdown.extension.italic.indicator": "_", "markdown.extension.orderedList.marker": "one", - "remote.SSH.enableAgentForwarding": true, "[json]": { "editor.defaultFormatter": "biomejs.biome" }, @@ -52,7 +51,6 @@ "GITHUB_TOKEN": "${localEnv:GITHUB_TOKEN}" }, "features": { - "ghcr.io/devcontainers/features/github-cli:1": {}, - "ghcr.io/devcontainers-contrib/features/prettier:1": {} + "ghcr.io/devcontainers-community/npm-features/prettier": {} } } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 948c0003..9524be92 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,6 +54,8 @@ jobs: module-change-exclude-patterns: .gitignore,*.md,*.tftest.hcl,tests/**,examples/** module-asset-exclude-patterns: .gitignore,*.md,*.tftest.hcl,tests/** use-ssh-source-format: true + tag-directory-separator: "-" + use-version-prefix: false - name: Test Action Outputs id: test-outputs diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 19e2f655..4cf426c9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -42,7 +42,7 @@ jobs: - name: Lint Codebase id: super-linter - uses: super-linter/super-linter/slim@v7 + uses: super-linter/super-linter@12150456a73e248bdc94d0794898f94e23127c88 # v7.4.0 env: DEFAULT_BRANCH: main GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-start.yml b/.github/workflows/release-start.yml index abfb5db7..1c498dc9 100644 --- a/.github/workflows/release-start.yml +++ b/.github/workflows/release-start.yml @@ -149,7 +149,7 @@ jobs: } - name: Create Branch and Pull Request - uses: peter-evans/create-pull-request@v7 + uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 with: token: ${{ steps.app-token.outputs.token }} base: main diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7b87f0e0..82544f88 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,9 +10,9 @@ on: types: [opened, synchronize, reopened] jobs: - tests: + Tests: runs-on: ubuntu-latest - name: Test + name: TypeScript Tests permissions: contents: read steps: @@ -33,9 +33,10 @@ jobs: - name: Run Tests Typescript run: npm run test env: - GITHUB_TOKEN: ${{ secrets.GH_TOKEN_REPO_CI_TESTING }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Note: SonarQube requires the results from tests to get the coverage report - name: SonarQube Scan - uses: SonarSource/sonarqube-scan-action@v5 + uses: SonarSource/sonarqube-scan-action@2500896589ef8f7247069a56136f8dc177c27ccf # v5 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/README.md b/README.md index 16227690..44577c50 100644 --- a/README.md +++ b/README.md @@ -194,12 +194,14 @@ configuring the following optional input parameters as needed. | `delete-legacy-tags` | Specifies a boolean that determines whether tags and releases from Terraform modules that have been deleted should be automatically removed | `true` | | `disable-wiki` | Whether to disable wiki generation for Terraform modules | `false` | | `wiki-sidebar-changelog-max` | An integer that specifies how many changelog entries are displayed in the sidebar per module | `5` | +| `wiki-usage-template` | A raw, multi-line string to override the default 'Usage' section in the generated wiki. Allows using variables like {{module_name}}, {{latest_tag}}, {{latest_tag_version_number}} and more.
[Read more here](#configuring-the-usage-template) | [See action.yml](https://github.com/polleuretan/terraform-module-releaser/blob/main/action.yml#L108) | | `disable-branding` | Controls whether a small branding link to the action's repository is added to PR comments. Recommended to leave enabled to support OSS. | `false` | | `module-path-ignore` | Comma-separated list of module paths to completely ignore. Modules matching any pattern here are excluded from all versioning, releases, and documentation.
[Read more here](#understanding-the-filtering-options) | `` (empty string) | | `module-change-exclude-patterns` | Comma-separated list of file patterns (relative to each module) to exclude from triggering version changes. Lets you release a module but control which files inside it do not force a version bump.
[Read more here](#understanding-the-filtering-options) | `.gitignore,*.md,*.tftest.hcl,tests/**` | | `module-asset-exclude-patterns` | A comma-separated list of file patterns to exclude when bundling a Terraform module for tag/release. Patterns follow glob syntax (e.g., `tests/\*\*`) and are relative to each Terraform module directory. Files matching these patterns will be excluded from the bundled output. | `.gitignore,*.md,*.tftest.hcl,tests/**` | | `use-ssh-source-format` | If enabled, all links to source code in generated Wiki documentation will use SSH standard format (e.g., `git::ssh://git@github.com/owner/repo.git`) instead of HTTPS format (`git::https://github.com/owner/repo.git`) | `false` | -| `wiki-usage-template` | A raw, multi-line string to override the default 'Usage' section in the generated wiki. Allows using variables like {{module_name}}, {{latest_tag}}, {{latest_tag_version_number}} and more.
[Read more here](#configuring-the-usage-template) | [See action.yml](https://github.com/polleuretan/terraform-module-releaser/blob/main/action.yml#L108) | +| `tag-directory-separator` | Character used to separate directory path components in Git tags. Supports `/`, `-`, `_`, or `.` | `/` | +| `use-version-prefix` | Whether to include the 'v' prefix on version tags (e.g., v1.2.3 vs 1.2.3) | `true` | ### Understanding the filtering options diff --git a/__mocks__/@actions/core.ts b/__mocks__/@actions/core.ts index e47f4769..9bb718e2 100644 --- a/__mocks__/@actions/core.ts +++ b/__mocks__/@actions/core.ts @@ -14,13 +14,13 @@ type MockedFunction = TFunc extends (...args: infer TArgs) => infer TRetu * Writes a debug message to the user log. * @param message - The debug message to log. */ -export const debug: MockedFunction<(message: string) => void> = vi.fn((message: string): void => {}); +export const debug: MockedFunction<(message: string) => void> = vi.fn((_message: string): void => {}); /** * Writes an informational message to the log. * @param message - The info message to log. */ -export const info: MockedFunction<(message: string) => void> = vi.fn((message: string): void => {}); +export const info: MockedFunction<(message: string) => void> = vi.fn((_message: string): void => {}); /** * Adds a warning issue with optional annotation properties. @@ -28,7 +28,7 @@ export const info: MockedFunction<(message: string) => void> = vi.fn((message: s * @param properties - Optional properties to add to the annotation. */ export const warning: MockedFunction<(message: string | Error, properties?: AnnotationProperties) => void> = vi.fn( - (message: string | Error, properties?: AnnotationProperties): void => {}, + (_message: string | Error, _properties?: AnnotationProperties): void => {}, ); /** @@ -37,7 +37,7 @@ export const warning: MockedFunction<(message: string | Error, properties?: Anno * @param properties - Optional properties to add to the annotation. */ export const notice: MockedFunction<(message: string | Error, properties?: AnnotationProperties) => void> = vi.fn( - (message: string | Error, properties?: AnnotationProperties): void => {}, + (_message: string | Error, _properties?: AnnotationProperties): void => {}, ); /** @@ -46,7 +46,7 @@ export const notice: MockedFunction<(message: string | Error, properties?: Annot * @param properties - Optional properties to add to the annotation. */ export const error: MockedFunction<(message: string | Error, properties?: AnnotationProperties) => void> = vi.fn( - (message: string | Error, properties?: AnnotationProperties): void => {}, + (_message: string | Error, _properties?: AnnotationProperties): void => {}, ); /** @@ -55,13 +55,13 @@ export const error: MockedFunction<(message: string | Error, properties?: Annota * @param message - The error message or object. * @throws An error with the specified message. */ -export const setFailed: MockedFunction<(message: string | Error) => void> = vi.fn((message: string | Error) => {}); +export const setFailed: MockedFunction<(message: string | Error) => void> = vi.fn((_message: string | Error) => {}); /** * Begins a new output group. Output until the next `endGroup` will be foldable in this group. * @param name - The name of the output group. */ -export const startGroup: MockedFunction<(name: string) => void> = vi.fn((name: string): void => {}); +export const startGroup: MockedFunction<(name: string) => void> = vi.fn((_name: string): void => {}); /** * Ends the current output group. @@ -109,13 +109,13 @@ export const getMultilineInput: MockedFunction<(name: string, options?: InputOpt * Masks a value in the log. When the masked value appears in the log, it is replaced with asterisks. * @param secret - Value to mask */ -export const setSecret: MockedFunction<(secret: string) => void> = vi.fn((secret: string): void => {}); +export const setSecret: MockedFunction<(secret: string) => void> = vi.fn((_secret: string): void => {}); /** * Prepends the given path to the PATH environment variable. * @param inputPath - Path to prepend */ -export const addPath: MockedFunction<(inputPath: string) => void> = vi.fn((inputPath: string): void => {}); +export const addPath: MockedFunction<(inputPath: string) => void> = vi.fn((_inputPath: string): void => {}); /** * Sets env variable for this action and future actions in the job. @@ -123,14 +123,14 @@ export const addPath: MockedFunction<(inputPath: string) => void> = vi.fn((input * @param val - Value of the variable */ export const exportVariable: MockedFunction<(name: string, val: string) => void> = vi.fn( - (name: string, val: string): void => {}, + (_name: string, _val: string): void => {}, ); /** * Enables or disables the echoing of commands into stdout for the rest of the step. * @param enabled - True to enable echoing, false to disable */ -export const setCommandEcho: MockedFunction<(enabled: boolean) => void> = vi.fn((enabled: boolean): void => {}); +export const setCommandEcho: MockedFunction<(enabled: boolean) => void> = vi.fn((_enabled: boolean): void => {}); /** * Begin an output group. @@ -138,7 +138,7 @@ export const setCommandEcho: MockedFunction<(enabled: boolean) => void> = vi.fn( * @param fn - Function to execute within the output group */ export const group: MockedFunction<(name: string, fn: () => Promise) => Promise> = vi.fn( - async (name: string, fn: () => Promise): Promise => { + async (_name: string, fn: () => Promise): Promise => { await fn(); }, ); @@ -150,7 +150,7 @@ export const group: MockedFunction<(name: string, fn: () => Promise) => Pr * @param value - Value to store. Non-string values will be converted to a string via JSON.stringify */ export const saveState: MockedFunction<(name: string, value: string) => void> = vi.fn( - (name: string, value: string): void => {}, + (_name: string, _value: string): void => {}, ); /** @@ -158,7 +158,7 @@ export const saveState: MockedFunction<(name: string, value: string) => void> = * @param name - Name of the state to get * @returns string */ -export const getState: MockedFunction<(name: string) => string> = vi.fn((name: string): string => ''); +export const getState: MockedFunction<(name: string) => string> = vi.fn((_name: string): string => ''); /** * Gets whether Actions Step Debug is on or not @@ -172,7 +172,7 @@ export const isDebug: MockedFunction<() => boolean> = vi.fn((): boolean => false * @returns string */ export const getIDToken: MockedFunction<(audience?: string) => Promise> = vi.fn( - async (audience?: string): Promise => '', + async (_audience?: string): Promise => '', ); /** @@ -181,7 +181,7 @@ export const getIDToken: MockedFunction<(audience?: string) => Promise> * @param value - Value to store. Non-string values will be converted to a string via JSON.stringify */ export const setOutput: MockedFunction<(name: string, value: string) => void> = vi.fn( - (name: string, value: string): void => {}, + (_name: string, _value: string): void => {}, ); // Re-export types diff --git a/__mocks__/config.ts b/__mocks__/config.ts index 5c43f6b4..bd10312b 100644 --- a/__mocks__/config.ts +++ b/__mocks__/config.ts @@ -1,4 +1,7 @@ +import { setupTestInputs } from '@/tests/helpers/inputs'; import type { Config } from '@/types'; +import type { ActionInputMetadata } from '@/types'; +import { ACTION_INPUTS, createConfigFromInputs } from '@/utils/metadata'; /** * Configuration interface with added utility methods @@ -9,55 +12,24 @@ interface ConfigWithMethods extends Config { } /** - * Default configuration object. + * Load default configuration from action.yml. */ -const defaultConfig: Config = { - majorKeywords: ['BREAKING CHANGE', '!', 'MAJOR CHANGE'], - minorKeywords: ['feat', 'feature'], - patchKeywords: ['fix', 'chore'], - defaultFirstTag: 'v1.0.0', - terraformDocsVersion: 'v0.20.0', - deleteLegacyTags: false, - disableWiki: false, - wikiSidebarChangelogMax: 10, - disableBranding: false, - modulePathIgnore: ['tf-modules/kms/examples/complete'], - moduleChangeExcludePatterns: ['.gitignore', '*.md'], - moduleAssetExcludePatterns: ['tests/**', 'examples/**'], - githubToken: 'ghp_test_token_2c6912E7710c838347Ae178B4', - useSSHSourceFormat: false, - wikiUsageTemplate: ` - To use this module in your Terraform, refer to the below module example: - - \`\`\`hcl - module "{{module_name_terraform}}" { - source = "git::{{module_source}}?ref={{latest_tag}}" - - # See inputs below for additional required parameters - } - \`\`\` -` +const createDefaultConfig = (): Config => { + setupTestInputs(); + return createConfigFromInputs(); }; /** - * Valid configuration keys. + * Default configuration object loaded from action.yml. + */ +const defaultConfig: Config = createDefaultConfig(); + +/** + * Valid configuration keys derived from ACTION_INPUTS. */ -const validConfigKeys = [ - 'majorKeywords', - 'minorKeywords', - 'patchKeywords', - 'defaultFirstTag', - 'terraformDocsVersion', - 'deleteLegacyTags', - 'disableWiki', - 'wikiSidebarChangelogMax', - 'disableBranding', - 'modulePathIgnore', - 'moduleChangeExcludePatterns', - 'moduleAssetExcludePatterns', - 'githubToken', - 'useSSHSourceFormat', -] as const; +const validConfigKeys = (Object.values(ACTION_INPUTS) as ActionInputMetadata[]).map( + (metadata) => metadata.configKey, +) as Array; type ValidConfigKey = (typeof validConfigKeys)[number]; @@ -68,7 +40,7 @@ let currentConfig: Config = { ...defaultConfig }; * Config proxy handler. */ const configProxyHandler: ProxyHandler = { - set(target: ConfigWithMethods, key: string, value: unknown): boolean { + set(_target: ConfigWithMethods, key: string, value: unknown): boolean { if (!validConfigKeys.includes(key as ValidConfigKey)) { throw new Error(`Invalid config key: ${key}`); } @@ -77,7 +49,7 @@ const configProxyHandler: ProxyHandler = { const expectedValue = defaultConfig[typedKey]; if ((Array.isArray(expectedValue) && Array.isArray(value)) || typeof expectedValue === typeof value) { - // @ts-ignore - we know that the key is valid and that the value is correct + // @ts-expect-error - we know that the key is valid and that the value is correct currentConfig[typedKey] = value as typeof expectedValue; return true; } @@ -85,7 +57,7 @@ const configProxyHandler: ProxyHandler = { throw new TypeError(`Invalid value type for config key: ${key}`); }, - get(target: ConfigWithMethods, prop: string | symbol): unknown { + get(_target: ConfigWithMethods, prop: string | symbol): unknown { if (typeof prop === 'string') { if (prop === 'set') { return (overrides: Partial = {}) => { diff --git a/__mocks__/context.ts b/__mocks__/context.ts index 11ad91d0..5c0a9595 100644 --- a/__mocks__/context.ts +++ b/__mocks__/context.ts @@ -59,7 +59,7 @@ let currentContext: Context = { ...defaultContext }; * Context proxy handler */ const contextProxyHandler: ProxyHandler = { - set(target: ContextWithMethods, key: string, value: unknown): boolean { + set(_target: ContextWithMethods, key: string, value: unknown): boolean { if (!validContextKeys.includes(key as ValidContextKey)) { throw new Error(`Invalid context key: ${key}`); } @@ -68,7 +68,7 @@ const contextProxyHandler: ProxyHandler = { const expectedValue = defaultContext[typedKey]; if (typeof expectedValue === typeof value || (typedKey === 'octokit' && typeof value === 'object')) { - // @ts-ignore - we know the key is valid and value type is correct + // @ts-expect-error - we know the key is valid and value type is correct currentContext[typedKey] = value; return true; } @@ -76,7 +76,7 @@ const contextProxyHandler: ProxyHandler = { throw new TypeError(`Invalid value type for context key: ${key}`); }, - get(target: ContextWithMethods, prop: string | symbol): unknown { + get(_target: ContextWithMethods, prop: string | symbol): unknown { if (typeof prop === 'string') { if (prop === 'set') { return (overrides: Partial = {}) => { diff --git a/__tests__/_setup.ts b/__tests__/_setup.ts index 7a9ac6ff..da84e9c1 100644 --- a/__tests__/_setup.ts +++ b/__tests__/_setup.ts @@ -1,3 +1,4 @@ +import { setupTestInputs } from '@/tests/helpers/inputs'; import { afterEach, beforeEach, vi } from 'vitest'; // Mocked node modules (./__mocks__/*) @@ -20,11 +21,14 @@ const defaultEnvironmentVariables = { }; beforeEach(() => { - // Initialize environment + // Initialize GitHub mock pull request environment for (const [key, value] of Object.entries(defaultEnvironmentVariables)) { vi.stubEnv(key, value); } + // Set up action input defaults for testing + setupTestInputs(); + // Clear all mocked functions usage data and state vi.clearAllMocks(); }); diff --git a/__tests__/config.test.ts b/__tests__/config.test.ts index be14bc22..1cc95cf7 100644 --- a/__tests__/config.test.ts +++ b/__tests__/config.test.ts @@ -2,14 +2,14 @@ import { clearConfigForTesting, config, getConfig } from '@/config'; import { arrayInputs, booleanInputs, - inputToConfigKey, - inputToConfigKeyMap, + clearEnvironmentInput, + getConfigKey, optionalInputs, requiredInputs, + setupTestInputs, stringInputs, - stubInputEnv, } from '@/tests/helpers/inputs'; -import type { Config } from '@/types'; +import { VALID_TAG_DIRECTORY_SEPARATORS } from '@/utils/constants'; import { endGroup, getBooleanInput, getInput, info, startGroup } from '@actions/core'; import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; @@ -22,38 +22,40 @@ describe('config', () => { }); beforeEach(() => { + // The config is cached. To ensure each test starts with a clean slate, we implicity clear it. + // We don't do this globally in setup as it's not necessary for all tests. clearConfigForTesting(); - // Note: We don't stubInputEnv here as there are cases where we want to test default inputs and - // in this case we would have to do a full reset of the config. }); describe('input validation', () => { for (const input of requiredInputs) { it(`should throw error when required input "${input}" is missing`, () => { - stubInputEnv({ [input]: null }); - expect(() => getConfig()).toThrow(new Error(`Input required and not supplied: ${input}`)); + clearEnvironmentInput(input); + expect(() => getConfig()).toThrow( + new Error(`Failed to process input '${input}': Input required and not supplied: ${input}`), + ); expect(getInput).toHaveBeenCalled(); }); } for (const input of optionalInputs) { it(`should handle optional input "${input}" when not present`, () => { - stubInputEnv({ [input]: null }); + clearEnvironmentInput(input); // Simply verify it doesn't throw without the specific error object expect(() => getConfig()).not.toThrow(); // Get the config and check the actual value const config = getConfig(); - // Get the config key using the mapping directly if possible - const configKey = inputToConfigKeyMap[input] || inputToConfigKey(input); + // Get the config key using the new getConfigKey function + const configKey = getConfigKey(input); // Type-safe access using the mapping if (arrayInputs.includes(input)) { - // Cast configKey to keyof Config to ensure type safety - expect(config[configKey as keyof Config]).toEqual([]); + // Now configKey is properly typed as keyof Config + expect(config[configKey]).toEqual([]); } if (stringInputs.includes(input)) { - expect(config[configKey as keyof Config]).toEqual(''); + expect(config[configKey]).toEqual(''); } expect(getInput).toHaveBeenCalled(); @@ -62,10 +64,10 @@ describe('config', () => { for (const input of booleanInputs) { it(`should throw error when input "${input}" has an invalid boolean value`, () => { - stubInputEnv({ [input]: 'invalid-boolean' }); + setupTestInputs({ [input]: 'invalid-boolean' }); expect(() => getConfig()).toThrow( - new TypeError( - `Input does not meet YAML 1.2 "Core Schema" specification: ${input}\nSupport boolean input list: \`true | True | TRUE | false | False | FALSE\``, + new Error( + `Failed to process input '${input}': Input does not meet YAML 1.2 "Core Schema" specification: ${input}\nSupport boolean input list: \`true | True | TRUE | false | False | FALSE\``, ), ); expect(getBooleanInput).toHaveBeenCalled(); @@ -73,20 +75,20 @@ describe('config', () => { } it('should throw error when moduleChangeExcludePatterns includes *.tf', () => { - stubInputEnv({ 'module-change-exclude-patterns': '*.tf,tests/**' }); + setupTestInputs({ 'module-change-exclude-patterns': '*.tf,tests/**' }); expect(() => getConfig()).toThrow( new TypeError('Exclude patterns cannot contain "*.tf" as it is required for module detection'), ); }); it('should throw error when moduleAssetExcludePatterns includes *.tf', () => { - stubInputEnv({ 'module-asset-exclude-patterns': '*.tf,tests/**' }); + setupTestInputs({ 'module-asset-exclude-patterns': '*.tf,tests/**' }); expect(() => getConfig()).toThrow( new TypeError('Asset exclude patterns cannot contain "*.tf" as these files are required'), ); }); - it('should handle boolean conversions for various formats', async () => { + it('should handle boolean conversions for various formats', () => { const booleanCases = ['true', 'True', 'TRUE', 'false', 'False', 'FALSE']; for (const booleanValue of booleanCases) { @@ -100,36 +102,134 @@ describe('config', () => { return acc; }, {}); - stubInputEnv(booleanInputValuesTest); + setupTestInputs(booleanInputValuesTest); // Check the boolean conversion for each key in booleanInputs const config = getConfig(); for (const booleanInput of booleanInputs) { // Get config key from the mapping, which is already typed as keyof Config - const configKey = inputToConfigKeyMap[booleanInput]; + const configKey = getConfigKey(booleanInput); expect(config[configKey]).toBe(booleanValue.toLowerCase() === 'true'); } } }); + it('should handle array input parsing and deduplication', () => { + const arrayTestCases = [ + { input: 'item1,item2,item3', expected: ['item1', 'item2', 'item3'] }, + { input: ' item4 , item5 , item6 ', expected: ['item4', 'item5', 'item6'] }, + { input: 'item7,item7,item8', expected: ['item7', 'item8'] }, + { input: 'item10,,item11,,,item12', expected: ['item10', 'item11', 'item12'] }, + ]; + + for (const testCase of arrayTestCases) { + // Ensure we reset the configuration since this is looping inside the test + clearConfigForTesting(); + vi.unstubAllEnvs(); + + // Create test inputs for all array inputs + const arrayInputValuesTest = arrayInputs.reduce((acc: Record, key) => { + acc[key] = testCase.input; + return acc; + }, {}); + + setupTestInputs(arrayInputValuesTest); + + // Check array parsing for each array input + const config = getConfig(); + for (const arrayInput of arrayInputs) { + const configKey = getConfigKey(arrayInput); + expect(config[configKey]).toEqual(testCase.expected); + } + } + }); + + it('should throw error for required array inputs when empty string is provided', () => { + const requiredArrayInputs = arrayInputs.filter((input) => requiredInputs.includes(input)); + + for (const input of requiredArrayInputs) { + clearConfigForTesting(); + vi.unstubAllEnvs(); + + // Set the required array input to empty string + setupTestInputs({ [input]: '' }); + + expect(() => getConfig()).toThrow( + new Error(`Failed to process input '${input}': Input required and not supplied: ${input}`), + ); + } + }); + + it('should return empty array for optional array inputs when empty string is provided', () => { + const optionalArrayInputs = arrayInputs.filter((input) => optionalInputs.includes(input)); + + for (const input of optionalArrayInputs) { + clearConfigForTesting(); + vi.unstubAllEnvs(); + + // Set the optional array input to empty string + setupTestInputs({ [input]: '' }); + + const config = getConfig(); + const configKey = getConfigKey(input); + expect(config[configKey]).toEqual([]); + } + }); + it('should throw error for non-numeric wiki-sidebar-changelog-max', () => { - stubInputEnv({ 'wiki-sidebar-changelog-max': 'invalid' }); + setupTestInputs({ 'wiki-sidebar-changelog-max': 'invalid' }); expect(() => getConfig()).toThrow( new TypeError('Wiki Sidebar Change Log Max must be an integer greater than or equal to one'), ); }); it('should throw error for 0 wiki-sidebar-changelog-max', () => { - stubInputEnv({ 'wiki-sidebar-changelog-max': '0' }); + setupTestInputs({ 'wiki-sidebar-changelog-max': '0' }); expect(() => getConfig()).toThrow( new TypeError('Wiki Sidebar Change Log Max must be an integer greater than or equal to one'), ); }); + + it('should throw error for invalid tag directory separator length', () => { + setupTestInputs({ 'tag-directory-separator': 'ab' }); + expect(() => getConfig()).toThrow(new TypeError('Tag directory separator must be exactly one character')); + }); + + it('should throw error for invalid tag directory separator character', () => { + setupTestInputs({ 'tag-directory-separator': '@' }); + expect(() => getConfig()).toThrow( + new TypeError(`Tag directory separator must be one of: ${VALID_TAG_DIRECTORY_SEPARATORS.join(', ')}. Got: '@'`), + ); + }); + + it('should allow valid tag directory separators', () => { + for (const separator of VALID_TAG_DIRECTORY_SEPARATORS) { + clearConfigForTesting(); + vi.unstubAllEnvs(); + setupTestInputs({ 'tag-directory-separator': separator }); + const config = getConfig(); + expect(config.tagDirectorySeparator).toBe(separator); + } + }); + + it('should throw error for invalid default first tag format', () => { + setupTestInputs({ 'default-first-tag': 'invalid-tag' }); + expect(() => getConfig()).toThrow( + new TypeError( + "Default first tag must be in format v#.#.# or #.#.# (e.g., v1.0.0 or 1.0.0). Got: 'invalid-tag'", + ), + ); + + clearConfigForTesting(); + setupTestInputs({ 'default-first-tag': 'v1.0' }); + expect(() => getConfig()).toThrow( + new TypeError("Default first tag must be in format v#.#.# or #.#.# (e.g., v1.0.0 or 1.0.0). Got: 'v1.0'"), + ); + }); }); describe('initialization', () => { it('should maintain singleton instance across multiple imports', () => { - stubInputEnv(); const firstInstance = getConfig(); const secondInstance = getConfig(); expect(firstInstance).toBe(secondInstance); @@ -137,47 +237,50 @@ describe('config', () => { expect(endGroup).toHaveBeenCalledTimes(1); }); - it('should initialize with valid inputs and log configuration', () => { - stubInputEnv(); + it('should initialize with valid default inputs', () => { const config = getConfig(); - expect(config.majorKeywords).toEqual(['MAJOR CHANGE', 'BREAKING CHANGE', '!']); + expect(config.majorKeywords).toEqual(['major change', 'breaking change']); expect(config.minorKeywords).toEqual(['feat', 'feature']); - expect(config.patchKeywords).toEqual(['fix', 'chore']); - expect(config.defaultFirstTag).toBe('v0.1.0'); - expect(config.terraformDocsVersion).toBe('v0.19.0'); - expect(config.deleteLegacyTags).toBe(false); + expect(config.patchKeywords).toEqual(['fix', 'chore', 'docs']); + expect(config.defaultFirstTag).toBe('v1.0.0'); + expect(config.terraformDocsVersion).toBe('v0.20.0'); + expect(config.deleteLegacyTags).toBe(true); expect(config.disableWiki).toBe(false); - expect(config.wikiSidebarChangelogMax).toBe(10); + expect(config.wikiSidebarChangelogMax).toBe(5); expect(config.disableBranding).toBe(false); expect(config.githubToken).toBe('ghp_test_token_2c6912E7710c838347Ae178B4'); - expect(config.moduleChangeExcludePatterns).toEqual(['.gitignore', '*.md']); - expect(config.moduleAssetExcludePatterns).toEqual(['tests/**', 'examples/**']); - expect(config.modulePathIgnore).toEqual(['tf-modules/kms/examples/complete']); + expect(config.modulePathIgnore).toEqual([]); + expect(config.moduleChangeExcludePatterns).toEqual(['.gitignore', '*.md', '*.tftest.hcl', 'tests/**']); + expect(config.moduleAssetExcludePatterns).toEqual(['.gitignore', '*.md', '*.tftest.hcl', 'tests/**']); expect(config.useSSHSourceFormat).toBe(false); + expect(config.tagDirectorySeparator).toBe('/'); + expect(config.useVersionPrefix).toBe(true); + expect(startGroup).toHaveBeenCalledWith('Initializing Config'); expect(startGroup).toHaveBeenCalledTimes(1); expect(endGroup).toHaveBeenCalledTimes(1); expect(vi.mocked(info).mock.calls).toEqual([ - ['Major Keywords: MAJOR CHANGE, BREAKING CHANGE, !'], + ['Major Keywords: major change, breaking change'], ['Minor Keywords: feat, feature'], - ['Patch Keywords: fix, chore'], - ['Default First Tag: v0.1.0'], - ['Terraform Docs Version: v0.19.0'], - ['Delete Legacy Tags: false'], + ['Patch Keywords: fix, chore, docs'], + ['Default First Tag: v1.0.0'], + ['Terraform Docs Version: v0.20.0'], + ['Delete Legacy Tags: true'], ['Disable Wiki: false'], - ['Wiki Sidebar Changelog Max: 10'], - ['Module Paths to Ignore: tf-modules/kms/examples/complete'], - ['Module Change Exclude Patterns: .gitignore, *.md'], - ['Module Asset Exclude Patterns: tests/**, examples/**'], + ['Wiki Sidebar Changelog Max: 5'], + ['Module Paths to Ignore: '], + ['Module Change Exclude Patterns: .gitignore, *.md, *.tftest.hcl, tests/**'], + ['Module Asset Exclude Patterns: .gitignore, *.md, *.tftest.hcl, tests/**'], ['Use SSH Source Format: false'], + ['Tag Directory Separator: /'], + ['Use Version Prefix: true'], ]); }); }); describe('config proxy', () => { it('should proxy config properties', () => { - stubInputEnv(); const proxyMajorKeywords = config.majorKeywords; const getterMajorKeywords = getConfig().majorKeywords; expect(proxyMajorKeywords).toEqual(getterMajorKeywords); @@ -196,32 +299,4 @@ describe('config', () => { expect(info).not.toHaveBeenCalled(); }); }); - - describe('input formatting', () => { - it('should handle various whitespace and duplicates in comma-separated inputs', () => { - stubInputEnv({ - 'major-keywords': ' BREAKING CHANGE , ! ', - 'minor-keywords': '\tfeat,\nfeature\r,feat', - }); - const config = getConfig(); - expect(config.majorKeywords).toEqual(['BREAKING CHANGE', '!']); - expect(config.minorKeywords).toEqual(['feat', 'feature']); - }); - - it('should filter out empty items in arrays', async () => { - stubInputEnv({ - 'major-keywords': 'BREAKING CHANGE,,!,,,', - 'module-change-exclude-patterns': ',.gitignore,,*.md,,', - }); - const config = getConfig(); - expect(config.majorKeywords).toEqual(['BREAKING CHANGE', '!']); - expect(config.moduleChangeExcludePatterns).toEqual(['.gitignore', '*.md']); - }); - - it('should handle empty modulePathIgnore', () => { - stubInputEnv({ 'module-path-ignore': '' }); - const config = getConfig(); - expect(config.modulePathIgnore).toEqual([]); - }); - }); }); diff --git a/__tests__/context.test.ts b/__tests__/context.test.ts index f837d46f..923a4830 100644 --- a/__tests__/context.test.ts +++ b/__tests__/context.test.ts @@ -4,7 +4,7 @@ import { createPullRequestMock } from '@/mocks/context'; import { info, startGroup } from '@actions/core'; import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; -// Mock node:fs. (Note: Appears we can't spy on functions via node:fs) +// Mock node:fs required for reading pull-request file information (Note: Appears we can't spy on functions via node:fs) vi.mock('node:fs', async () => { const original = await vi.importActual('node:fs'); return { @@ -46,7 +46,9 @@ describe('context', () => { describe('environment variable validation', () => { for (const envVar of requiredEnvVars) { it(`should throw an error if ${envVar} is not set`, () => { + // Set the specific environment variable to undefined, but keep others set vi.stubEnv(envVar, undefined); + expect(() => getContext()).toThrow( new Error( `The ${envVar} environment variable is missing or invalid. This variable should be automatically set by GitHub for each workflow run. If this variable is missing or not correctly set, it indicates a serious issue with the GitHub Actions environment, potentially affecting the execution of subsequent steps in the workflow. Please review the workflow setup or consult the documentation for proper configuration.`, @@ -182,9 +184,6 @@ describe('context', () => { const customApiUrl = 'https://github.example.com/api/v3'; vi.stubEnv('GITHUB_API_URL', customApiUrl); - // Clear context to force reinitialization - clearContextForTesting(); - const context = getContext(); // Check that the context was created (which means the custom API URL was used) @@ -196,9 +195,6 @@ describe('context', () => { // Ensure GITHUB_API_URL is not set to test the default fallback vi.stubEnv('GITHUB_API_URL', undefined); - // Clear context to force reinitialization - clearContextForTesting(); - const context = getContext(); // Check that the context was created with default API URL @@ -220,7 +216,7 @@ describe('context', () => { vi.mocked(startGroup).mockClear(); // Second access should not trigger initialization - const prNumber = context.prNumber; // Intentionally access a property with no usage + const _prNumber = context.prNumber; // Intentionally access a property with no usage expect(startGroup).not.toHaveBeenCalled(); expect(info).not.toHaveBeenCalled(); }); diff --git a/__tests__/helpers/action-defaults.ts b/__tests__/helpers/action-defaults.ts new file mode 100644 index 00000000..ff549e93 --- /dev/null +++ b/__tests__/helpers/action-defaults.ts @@ -0,0 +1,48 @@ +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { ACTION_INPUTS } from '@/utils/metadata'; +import * as yaml from 'js-yaml'; + +/** + * Interface for action.yml structure + */ +interface ActionYml { + inputs: Record< + string, + { + description: string; + required: boolean; + default?: string; + } + >; +} + +/** + * Gets action defaults from action.yml file. + * Returns a record of all input names to their default values (or undefined if no default). + */ +/** + * Extracts default values for GitHub Action inputs from action.yml file. + * + * This function reads the action.yml file from the current working directory, + * parses its content, and retrieves the default values for all inputs defined + * in ACTION_INPUTS. + * + * @returns A record mapping each input name to its default value from the action.yml file. + * If an input has no default value, its entry will contain undefined. + */ +export function getActionDefaults(): Record { + const actionYmlPath = path.join(process.cwd(), 'action.yml'); + const actionYmlContent = fs.readFileSync(actionYmlPath, 'utf8'); + const actionYml = yaml.load(actionYmlContent) as ActionYml; + + const defaults: Record = {}; + + // Process all inputs from ACTION_INPUTS to ensure we have entries for all inputs + for (const [inputName] of Object.entries(ACTION_INPUTS)) { + const actionInput = actionYml.inputs[inputName]; + defaults[inputName] = actionInput?.default; + } + + return defaults; +} diff --git a/__tests__/helpers/inputs.ts b/__tests__/helpers/inputs.ts index 5fa41ad0..0801dbdd 100644 --- a/__tests__/helpers/inputs.ts +++ b/__tests__/helpers/inputs.ts @@ -1,121 +1,89 @@ +import { getActionDefaults } from '@/tests/helpers/action-defaults'; import type { Config } from '@/types'; +import { ACTION_INPUTS } from '@/utils/metadata'; import { vi } from 'vitest'; -const INPUT_KEY = 'INPUT_'; +// Load action defaults once globally +const ACTION_DEFAULTS = getActionDefaults(); + +// Generate input type arrays from centralized metadata +export const requiredInputs = Object.entries(ACTION_INPUTS) + .filter(([, metadata]) => metadata.required) + .map(([inputName]) => inputName); + +export const optionalInputs = Object.entries(ACTION_INPUTS) + .filter(([, metadata]) => !metadata.required) + .map(([inputName]) => inputName); + +export const booleanInputs = Object.entries(ACTION_INPUTS) + .filter(([, metadata]) => metadata.type === 'boolean') + .map(([inputName]) => inputName); + +export const arrayInputs = Object.entries(ACTION_INPUTS) + .filter(([, metadata]) => metadata.type === 'array') + .map(([inputName]) => inputName); + +export const stringInputs = Object.entries(ACTION_INPUTS) + .filter(([, metadata]) => metadata.type === 'string') + .map(([inputName]) => inputName); + +export const numberInputs = Object.entries(ACTION_INPUTS) + .filter(([, metadata]) => metadata.type === 'number') + .map(([inputName]) => inputName); /** - * Type-safe mapping from input names to config keys. - * This ensures that each value is a valid key in the Config type. + * Converts an input name to its corresponding config key. + * @param inputName The input name (e.g., 'github_token', 'module-path-ignore') + * @returns The corresponding config key as a keyof Config */ -export const inputToConfigKeyMap: Record = { - 'major-keywords': 'majorKeywords', - 'minor-keywords': 'minorKeywords', - 'patch-keywords': 'patchKeywords', - 'default-first-tag': 'defaultFirstTag', - 'terraform-docs-version': 'terraformDocsVersion', - 'delete-legacy-tags': 'deleteLegacyTags', - 'disable-wiki': 'disableWiki', - 'wiki-sidebar-changelog-max': 'wikiSidebarChangelogMax', - 'disable-branding': 'disableBranding', - github_token: 'githubToken', - 'module-path-ignore': 'modulePathIgnore', - 'module-change-exclude-patterns': 'moduleChangeExcludePatterns', - 'module-asset-exclude-patterns': 'moduleAssetExcludePatterns', - 'use-ssh-source-format': 'useSSHSourceFormat', -}; +export function getConfigKey(inputName: string): keyof Config { + const metadata = ACTION_INPUTS[inputName]; + if (!metadata) { + throw new Error(`Unknown input: ${inputName}`); + } -// Create reverse mapping from config keys to input names -export const configKeyToInputMap = Object.entries(inputToConfigKeyMap).reduce( - (acc, [inputName, configKey]) => { - acc[configKey] = inputName; - return acc; - }, - {} as Record, -); + return metadata.configKey; +} -// Default inputs used for testing @actions/core behavior -export const defaultInputs = { - 'major-keywords': 'MAJOR CHANGE,BREAKING CHANGE,!', - 'minor-keywords': 'feat,feature', - 'patch-keywords': 'fix,chore', - 'default-first-tag': 'v0.1.0', - 'terraform-docs-version': 'v0.19.0', - 'delete-legacy-tags': 'false', - 'disable-wiki': 'false', - 'wiki-sidebar-changelog-max': '10', - 'disable-branding': 'false', - 'module-path-ignore': 'tf-modules/kms/examples/complete', - 'module-change-exclude-patterns': '.gitignore,*.md', - 'module-asset-exclude-patterns': 'tests/**,examples/**', - github_token: 'ghp_test_token_2c6912E7710c838347Ae178B4', - 'use-ssh-source-format': 'false', -}; -export const requiredInputs = [ - 'major-keywords', - 'minor-keywords', - 'patch-keywords', - 'default-first-tag', - 'terraform-docs-version', - 'delete-legacy-tags', - 'disable-wiki', - 'wiki-sidebar-changelog-max', - 'disable-branding', - 'github_token', - 'use-ssh-source-format', -]; -export const optionalInputs = Object.keys(defaultInputs).filter((key) => !requiredInputs.includes(key)); -export const booleanInputs = ['delete-legacy-tags', 'disable-wiki', 'disable-branding', 'use-ssh-source-format']; -export const arrayInputs = [ - 'major-keywords', - 'minor-keywords', - 'patch-keywords', - 'module-path-ignore', - 'module-change-exclude-patterns', - 'module-asset-exclude-patterns', -]; -export const stringInputs = ['default-first-tag', 'terraform-docs-version', 'github_token']; -export const numberInputs = ['wiki-sidebar-changelog-max']; +/** + * Converts an input name to its corresponding environment variable name. + * This is the exact inverse of what @actions/core getInput() does. + * @param inputName The input name (e.g., 'github_token', 'module-path-ignore') + * @returns The environment variable name (e.g., 'INPUT_GITHUB_TOKEN', 'INPUT_MODULE_PATH_IGNORE') + */ +function inputToEnvVar(inputName: string): string { + return `INPUT_${inputName.replace(/ /g, '_').toUpperCase()}`; +} /** - * Converts a dash-case input name to its corresponding camelCase config key - * Prefer using the inputToConfigKeyMap directly for known input keys + * Sets up test environment with action defaults and optional overrides. + * This replaces the previous stubInputEnv function with a cleaner approach + * that loads defaults from action.yml and applies test-specific overrides. * - * @param inputName The input name to convert - * @returns The corresponding config key as a string + * @param overrides - Test-specific overrides for input values. */ -export function inputToConfigKey(inputName: string): string { - // Check if the input name is in our mapping first - if (inputName in inputToConfigKeyMap) { - return inputToConfigKeyMap[inputName]; - } +export function setupTestInputs(overrides: Record = {}) { + // Start with action.yml defaults and apply test-specific defaults + const allInputs = { + ...ACTION_DEFAULTS, + github_token: 'ghp_test_token_2c6912E7710c838347Ae178B4', + ...overrides, + }; - // Fallback to the conversion logic - return inputName.replace(/-([a-z])/g, (_, c) => c.toUpperCase()); + // Set environment variables for all values (undefined is valid for vi.stubEnv) + for (const [inputName, value] of Object.entries(allInputs)) { + vi.stubEnv(inputToEnvVar(inputName), value); + } } /** - * Stubs environment variables with an `INPUT_` prefix using a set of default values, - * while allowing specific overrides. By default, this function sets a baseline of - * sane default environment values that would typically be used in tests. + * Clears a specific action input environment variable. * - * Overrides can be provided as key-value pairs, where: - * - A `string` value sets or replaces the environment variable. - * - A `null` value skips the setting, allowing for flexibility in customizing the stubbed environment. + * Useful for testing scenarios where you need to remove a specific input. Wrapper around + * vi.stubEnv which has an unsual syntax for clearing environment variables/ * - * @param {Record} overrides - An object specifying environment variable overrides. - * Keys in this object correspond to the environment variable names (without the `INPUT_` prefix), - * and values specify the desired values or `null` to skip setting. + * @param inputName The input name to clear (e.g., 'github_token', 'module-path-ignore') */ -export function stubInputEnv(inputs: Record = {}) { - // Merge default inputs with overrides, giving precedence to overrides - const mergedInputs = { ...defaultInputs, ...inputs }; - - for (const [key, value] of Object.entries(mergedInputs)) { - if (value === null) { - continue; - } - - const prefixedKey = `${INPUT_KEY}${key.replace(/ /g, '_').toUpperCase()}`; - vi.stubEnv(prefixedKey, value); - } +export function clearEnvironmentInput(inputName: string): void { + vi.stubEnv(inputToEnvVar(inputName), undefined); } diff --git a/__tests__/helpers/octokit.ts b/__tests__/helpers/octokit.ts index 4438055f..e5cb69aa 100644 --- a/__tests__/helpers/octokit.ts +++ b/__tests__/helpers/octokit.ts @@ -1,5 +1,5 @@ import type { OctokitRestApi } from '@/types'; -import { trimSlashes } from '@/utils/string'; +import { removeTrailingCharacters } from '@/utils/string'; import { paginateRest } from '@octokit/plugin-paginate-rest'; import { restEndpointMethods } from '@octokit/plugin-rest-endpoint-methods'; import type { RestEndpointMethodTypes } from '@octokit/plugin-rest-endpoint-methods'; @@ -384,5 +384,6 @@ function getLinkHeader(slug: string, page: number, perPage: number, totalCount: const nextPage = page + 1; const lastPage = totalPages; - return `; rel="next", ; rel="last"`; + const slugTrimmed = removeTrailingCharacters(slug, ['/']); + return `; rel="next", ; rel="last"`; } diff --git a/__tests__/tags.test.ts b/__tests__/tags.test.ts index 44a05ae2..51836ddf 100644 --- a/__tests__/tags.test.ts +++ b/__tests__/tags.test.ts @@ -53,7 +53,7 @@ describe('tags', () => { expect(debugCall).toBeDefined(); // Ensure there is a debug call const debugMessage = debugCall[0]; expect(/^Total page requests: \d+$/.test(debugMessage)).toBe(true); // Check if it matches the format - expect(Number.parseInt(debugMessage.split(': ')[1])).toBeGreaterThan(1); // Check if number > 1 + expect(Number.parseInt(debugMessage.split(': ')[1], 10)).toBeGreaterThan(1); // Check if number > 1 // Check the first info call for "Found X tags" const infoCall = vi.mocked(info).mock.calls[0]; // Get the first call @@ -270,7 +270,7 @@ describe('tags', () => { await expect(deleteTags(tagsToDelete)).rejects.toThrow( `Failed to delete repository tag: v1.0.0 Resource not accessible by integration. -Ensure that the GitHub Actions workflow has the correct permissions to delete tags by ensuring that your workflow YAML file has the following block under \"permissions\": +Ensure that the GitHub Actions workflow has the correct permissions to delete tags by ensuring that your workflow YAML file has the following block under "permissions": permissions: contents: write`, diff --git a/__tests__/templating.test.ts b/__tests__/templating.test.ts deleted file mode 100644 index 82c62d4e..00000000 --- a/__tests__/templating.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { render } from '../src/templating'; - -describe('templating', () => { - it('should replace a single placeholder', () => { - const template = 'Hello, {{name}}!'; - const variables = { name: 'World' }; - const result = render(template, variables); - expect(result).toBe('Hello, World!'); - }); - - it('should replace multiple placeholders', () => { - const template = '{{greeting}}, {{name}}!'; - const variables = { greeting: 'Hi', name: 'There' }; - const result = render(template, variables); - expect(result).toBe('Hi, There!'); - }); - - it('should handle templates with no placeholders', () => { - const template = 'Just a plain string.'; - const variables = { name: 'World' }; - const result = render(template, variables); - expect(result).toBe('Just a plain string.'); - }); - - it('should handle empty string values', () => { - const template = 'A{{key}}B'; - const variables = { key: '' }; - const result = render(template, variables); - expect(result).toBe('AB'); - }); - - it('should leave unmapped placeholders untouched', () => { - const template = 'Hello, {{name}} and {{unmapped}}!'; - const variables = { name: 'World' }; - const result = render(template, variables); - expect(result).toBe('Hello, World and {{unmapped}}!'); - }); -}); diff --git a/__tests__/terraform-docs.test.ts b/__tests__/terraform-docs.test.ts index df851ef4..214114d6 100644 --- a/__tests__/terraform-docs.test.ts +++ b/__tests__/terraform-docs.test.ts @@ -251,7 +251,7 @@ describe('terraform-docs', async () => { for (const file of cleanupFiles) { try { unlinkSync(file); - } catch (err) { + } catch (_err) { // Ignore cleanup errors } } diff --git a/__tests__/terraform-module.test.ts b/__tests__/terraform-module.test.ts index 13818d25..7be18aaa 100644 --- a/__tests__/terraform-module.test.ts +++ b/__tests__/terraform-module.test.ts @@ -33,6 +33,7 @@ describe('TerraformModule', () => { defaultFirstTag: 'v0.1.0', moduleChangeExcludePatterns: [], modulePathIgnore: [], + useVersionPrefix: true, }); }); @@ -57,7 +58,7 @@ describe('TerraformModule', () => { mkdirSync(specialDir, { recursive: true }); const module = new TerraformModule(specialDir); - expect(module.name).toBe('complex_module-name-with/chars'); + expect(module.name).toBe('complex_module-name.with/chars'); }); it('should handle nested directory paths', () => { @@ -245,6 +246,38 @@ describe('TerraformModule', () => { expect(module.getLatestTagVersion()).toBeNull(); }); + it('should handle tags with different separators in getLatestTagVersion', () => { + // Test with different separators to ensure regex works correctly + module.setTags(['tf-modules/test-module/v1.0.0']); + expect(module.getLatestTagVersion()).toBe('v1.0.0'); + + // Test with hyphen separator + module.setTags(['tf-modules-test-module-v2.0.0']); + expect(module.getLatestTagVersion()).toBe('v2.0.0'); + + // Test with underscore separator + module.setTags(['tf-modules_test_module_v3.0.0']); + expect(module.getLatestTagVersion()).toBe('v3.0.0'); + + // Test with dot separator + module.setTags(['tf-modules.test.module.v4.0.0']); + expect(module.getLatestTagVersion()).toBe('v4.0.0'); + + // Test without v prefix + module.setTags(['tf-modules/test-module/5.0.0']); + expect(module.getLatestTagVersion()).toBe('5.0.0'); + }); + + it('should return null when latest tag does not match MODULE_TAG_REGEX', () => { + // Mock getLatestTag to return an invalid format that won't match the regex + vi.spyOn(module, 'getLatestTag').mockReturnValue('invalid-tag-format'); + + expect(module.getLatestTagVersion()).toBeNull(); + + // Restore the original method + vi.restoreAllMocks(); + }); + it('should handle complex version sorting', () => { const tags = [ 'tf-modules/test-module/v1.2.10', @@ -275,14 +308,14 @@ describe('TerraformModule', () => { it('should throw error for tag with no slash (invalid format)', () => { const tags = ['v1.2.3']; expect(() => module.setTags(tags)).toThrow( - "Invalid tag format: 'v1.2.3'. Expected format: 'tf-modules/test-module/v#.#.#' or 'tf-modules/test-module/#.#.#' for module.", + "Invalid tag format: 'v1.2.3'. Expected format: 'tf-modules/test-module[separator]v#.#.#' or 'tf-modules/test-module[separator]#.#.#'.", ); }); it('should throw error for tag with incorrect module name', () => { const tags = ['foo/bar/v9.8.7']; expect(() => module.setTags(tags)).toThrow( - "Invalid tag format: 'foo/bar/v9.8.7'. Expected format: 'tf-modules/test-module/v#.#.#' or 'tf-modules/test-module/#.#.#' for module.", + "Invalid tag format: 'foo/bar/v9.8.7'. Expected format: 'tf-modules/test-module[separator]v#.#.#' or 'tf-modules/test-module[separator]#.#.#'.", ); }); @@ -304,7 +337,6 @@ describe('TerraformModule', () => { expect(module.tags[1]).toBe('tf-modules/test-module/1.2.3'); }); - // Add tests for extractVersionFromTag describe('extractVersionFromTag()', () => { let module: TerraformModule; @@ -473,7 +505,7 @@ describe('TerraformModule', () => { ]; expect(() => module.setReleases(releases)).toThrow( - "Invalid tag format: 'tf-modules/test-module/v1.0'. Expected format: 'tf-modules/test-module/v#.#.#' or 'tf-modules/test-module/#.#.#' for module.", + "Invalid tag format: 'tf-modules/test-module/v1.0'. Expected format: 'tf-modules/test-module[separator]v#.#.#' or 'tf-modules/test-module[separator]#.#.#'.", ); }); @@ -494,7 +526,7 @@ describe('TerraformModule', () => { ]; expect(() => module.setReleases(releases)).toThrow( - "Invalid tag format: 'tf-modules/test-module/vbeta.1.0'. Expected format: 'tf-modules/test-module/v#.#.#' or 'tf-modules/test-module/#.#.#' for module.", + "Invalid tag format: 'tf-modules/test-module/vbeta.1.0'. Expected format: 'tf-modules/test-module[separator]v#.#.#' or 'tf-modules/test-module[separator]#.#.#'.", ); }); @@ -788,6 +820,38 @@ describe('TerraformModule', () => { "Invalid version format: 'invalid-format'. Expected v#.#.# or #.#.# format.", ); }); + + it('should respect useVersionPrefix setting when true (with v prefix)', () => { + // Set useVersionPrefix to true + config.set({ + useVersionPrefix: true, + }); + + module.setTags(['tf-modules/test-module/v1.2.3']); + module.addCommit({ + sha: 'abc123', + message: 'fix: bug fix', + files: ['main.tf'], + }); + + expect(module.getReleaseTagVersion()).toBe('v1.2.4'); + }); + + it('should respect useVersionPrefix setting when false (without v prefix)', () => { + // Set useVersionPrefix to false + config.set({ + useVersionPrefix: false, + }); + + module.setTags(['tf-modules/test-module/v1.2.3']); + module.addCommit({ + sha: 'abc123', + message: 'fix: bug fix', + files: ['main.tf'], + }); + + expect(module.getReleaseTagVersion()).toBe('1.2.4'); // No 'v' prefix + }); }); describe('getReleaseTag()', () => { @@ -885,44 +949,222 @@ describe('TerraformModule', () => { describe('static utilities', () => { describe('getTerraformModuleNameFromRelativePath()', () => { - it('should generate valid module names from paths', () => { - expect(TerraformModule.getTerraformModuleNameFromRelativePath('tf-modules/simple-module')).toBe( - 'tf-modules/simple-module', - ); + beforeEach(() => { + // Reset to default config for each test + config.set({ + tagDirectorySeparator: '/', + majorKeywords: ['BREAKING CHANGE', 'major change'], + minorKeywords: ['feat:', 'feature:'], + defaultFirstTag: 'v0.1.0', + moduleChangeExcludePatterns: [], + modulePathIgnore: [], + useVersionPrefix: true, + }); + }); - expect(TerraformModule.getTerraformModuleNameFromRelativePath('complex_module-name.with/chars')).toBe( - 'complex_module-name-with/chars', - ); + describe('with different tag directory separators', () => { + it('should use forward slash separator by default', () => { + config.set({ tagDirectorySeparator: '/' }); + expect(TerraformModule.getTerraformModuleNameFromRelativePath('tf-modules/simple-module')).toBe( + 'tf-modules/simple-module', + ); + expect(TerraformModule.getTerraformModuleNameFromRelativePath('complex\\module\\windows\\path')).toBe( + 'complex/module/windows/path', + ); + }); - expect(TerraformModule.getTerraformModuleNameFromRelativePath('/leading/slash/')).toBe('leading/slash'); + it('should use hyphen separator when configured', () => { + config.set({ tagDirectorySeparator: '-' }); + expect(TerraformModule.getTerraformModuleNameFromRelativePath('tf-modules/simple-module')).toBe( + 'tf-modules-simple-module', + ); + expect(TerraformModule.getTerraformModuleNameFromRelativePath('complex\\module\\windows\\path')).toBe( + 'complex-module-windows-path', + ); + }); - expect(TerraformModule.getTerraformModuleNameFromRelativePath('module...with...dots')).toBe('module-with-dots'); - }); + it('should use underscore separator when configured', () => { + config.set({ tagDirectorySeparator: '_' }); + expect(TerraformModule.getTerraformModuleNameFromRelativePath('tf-modules/simple-module')).toBe( + 'tf-modules_simple-module', + ); + expect(TerraformModule.getTerraformModuleNameFromRelativePath('complex\\module\\windows\\path')).toBe( + 'complex_module_windows_path', + ); + }); - it('should handle leading and trailing slashes', () => { - expect(TerraformModule.getTerraformModuleNameFromRelativePath('/test-module/')).toBe('test-module'); + it('should use dot separator when configured', () => { + config.set({ tagDirectorySeparator: '.' }); + expect(TerraformModule.getTerraformModuleNameFromRelativePath('tf-modules/simple-module')).toBe( + 'tf-modules.simple-module', + ); + expect(TerraformModule.getTerraformModuleNameFromRelativePath('complex\\module\\windows\\path')).toBe( + 'complex.module.windows.path', + ); + }); }); - it('should handle multiple consecutive slashes', () => { - expect(TerraformModule.getTerraformModuleNameFromRelativePath('tf-modules//vpc//endpoint')).toBe( - 'tf-modules/vpc/endpoint', - ); - }); + describe('character normalization and cleanup', () => { + beforeEach(() => { + config.set({ tagDirectorySeparator: '/' }); + }); - it('should handle whitespace', () => { - expect(TerraformModule.getTerraformModuleNameFromRelativePath(' test module ')).toBe('test-module'); - }); + it('should normalize Windows backslashes to configured separator', () => { + expect(TerraformModule.getTerraformModuleNameFromRelativePath('windows\\path\\module')).toBe( + 'windows/path/module', + ); + expect(TerraformModule.getTerraformModuleNameFromRelativePath('mixed\\and/path\\separators')).toBe( + 'mixed/and/path/separators', + ); + }); - it('should convert to lowercase', () => { - expect(TerraformModule.getTerraformModuleNameFromRelativePath('Test-Module')).toBe('test-module'); - }); + it('should convert to lowercase', () => { + expect(TerraformModule.getTerraformModuleNameFromRelativePath('Test-Module')).toBe('test-module'); + expect(TerraformModule.getTerraformModuleNameFromRelativePath('UPPERCASE/MODULE')).toBe('uppercase/module'); + }); + + it('should replace invalid characters with hyphens', () => { + expect(TerraformModule.getTerraformModuleNameFromRelativePath('test@module!#$')).toBe('test-module'); + expect(TerraformModule.getTerraformModuleNameFromRelativePath('module%with&special*chars')).toBe( + 'module-with-special-chars', + ); + expect(TerraformModule.getTerraformModuleNameFromRelativePath('test module with spaces')).toBe( + 'test-module-with-spaces', + ); + }); + + it('should normalize consecutive special characters', () => { + expect(TerraformModule.getTerraformModuleNameFromRelativePath('module...with...dots')).toBe( + 'module.with.dots', + ); + expect(TerraformModule.getTerraformModuleNameFromRelativePath('tf-modules//vpc//endpoint')).toBe( + 'tf-modules/vpc/endpoint', + ); + expect(TerraformModule.getTerraformModuleNameFromRelativePath('module---with---hyphens')).toBe( + 'module-with-hyphens', + ); + expect(TerraformModule.getTerraformModuleNameFromRelativePath('module___with___underscores')).toBe( + 'module_with_underscores', + ); + }); - it('should clean up invalid characters', () => { - expect(TerraformModule.getTerraformModuleNameFromRelativePath('test@module!#$')).toBe('test-module'); + it('should remove leading and trailing special characters', () => { + expect(TerraformModule.getTerraformModuleNameFromRelativePath('/leading/slash/')).toBe('leading/slash'); + expect(TerraformModule.getTerraformModuleNameFromRelativePath('...leading.dots')).toBe('leading.dots'); + expect(TerraformModule.getTerraformModuleNameFromRelativePath('trailing.dots...')).toBe('trailing.dots'); + expect(TerraformModule.getTerraformModuleNameFromRelativePath('---leading-hyphens')).toBe('leading-hyphens'); + expect(TerraformModule.getTerraformModuleNameFromRelativePath('trailing-hyphens---')).toBe( + 'trailing-hyphens', + ); + expect(TerraformModule.getTerraformModuleNameFromRelativePath('___leading_underscores')).toBe( + 'leading_underscores', + ); + expect(TerraformModule.getTerraformModuleNameFromRelativePath('trailing_underscores___')).toBe( + 'trailing_underscores', + ); + expect(TerraformModule.getTerraformModuleNameFromRelativePath('/.-_mixed_leading')).toBe('mixed_leading'); + expect(TerraformModule.getTerraformModuleNameFromRelativePath('mixed_trailing_.-/')).toBe('mixed_trailing'); + }); + + it('should handle edge cases', () => { + expect(TerraformModule.getTerraformModuleNameFromRelativePath(' whitespace ')).toBe('whitespace'); + expect(TerraformModule.getTerraformModuleNameFromRelativePath('/test-module/')).toBe('test-module'); + expect(TerraformModule.getTerraformModuleNameFromRelativePath('single')).toBe('single'); + expect(TerraformModule.getTerraformModuleNameFromRelativePath('a')).toBe('a'); + }); }); - it('should remove trailing special characters', () => { - expect(TerraformModule.getTerraformModuleNameFromRelativePath('test-module-.')).toBe('test-module'); + describe('comprehensive scenarios with different separators', () => { + const testScenarios = [ + { + separator: '/', + input: 'tf-modules/aws/vpc-endpoint', + expected: 'tf-modules/aws/vpc-endpoint', + }, + { + separator: '-', + input: 'tf-modules/aws/vpc-endpoint', + expected: 'tf-modules-aws-vpc-endpoint', + }, + { + separator: '_', + input: 'tf-modules/aws/vpc-endpoint', + expected: 'tf-modules_aws_vpc-endpoint', + }, + { + separator: '.', + input: 'tf-modules/aws/vpc-endpoint', + expected: 'tf-modules.aws.vpc-endpoint', + }, + ]; + + for (const { separator, input, expected } of testScenarios) { + it(`should handle complex paths with ${separator} separator`, () => { + config.set({ tagDirectorySeparator: separator }); + expect(TerraformModule.getTerraformModuleNameFromRelativePath(input)).toBe(expected); + }); + } + + const complexTestScenarios = [ + { + separator: '/', + input: '//tf-modules//aws..vpc--endpoint__', + expected: 'tf-modules/aws.vpc-endpoint', + }, + { + separator: '-', + input: '//tf-modules//aws..vpc--endpoint__', + expected: 'tf-modules-aws.vpc-endpoint', + }, + { + separator: '_', + input: '//tf-modules//aws..vpc--endpoint__', + expected: 'tf-modules_aws.vpc-endpoint', + }, + { + separator: '.', + input: '//tf-modules//aws..vpc--endpoint__', + expected: 'tf-modules.aws.vpc-endpoint', + }, + ]; + + for (const { separator, input, expected } of complexTestScenarios) { + it(`should handle complex normalization with ${separator} separator`, () => { + config.set({ tagDirectorySeparator: separator }); + expect(TerraformModule.getTerraformModuleNameFromRelativePath(input)).toBe(expected); + }); + } + }); + + describe('real-world terraform module scenarios', () => { + it('should handle typical terraform module paths', () => { + config.set({ tagDirectorySeparator: '/' }); + + const testCases = [ + { input: 'modules/networking/vpc', expected: 'modules/networking/vpc' }, + { input: 'modules/compute/ec2-instance', expected: 'modules/compute/ec2-instance' }, + { input: 'modules/storage/s3-bucket', expected: 'modules/storage/s3-bucket' }, + { input: 'terraform/aws/rds_cluster', expected: 'terraform/aws/rds_cluster' }, + { input: 'tf-modules/azure/storage.account', expected: 'tf-modules/azure/storage.account' }, + ]; + + for (const { input, expected } of testCases) { + expect(TerraformModule.getTerraformModuleNameFromRelativePath(input)).toBe(expected); + } + }); + + it('should handle module paths with various separators configured', () => { + const separatorTests = [ + { separator: '-', input: 'modules/aws/vpc', expected: 'modules-aws-vpc' }, + { separator: '_', input: 'modules/aws/vpc', expected: 'modules_aws_vpc' }, + { separator: '.', input: 'modules/aws/vpc', expected: 'modules.aws.vpc' }, + ]; + + for (const { separator, input, expected } of separatorTests) { + config.set({ tagDirectorySeparator: separator }); + expect(TerraformModule.getTerraformModuleNameFromRelativePath(input)).toBe(expected); + } + }); }); }); @@ -954,17 +1196,48 @@ describe('TerraformModule', () => { TerraformModule.isModuleAssociatedWithTag('tf-modules/vpc-endpoint', 'tf-modules/vpc-endpoint/v1.0.0'), ).toBe(true); expect( - TerraformModule.isModuleAssociatedWithTag('tf-modules/vpc-endpoint', 'tf-modules/vpc-endpoint/1.0.0'), + TerraformModule.isModuleAssociatedWithTag('tf-modules/vpc-endpoint', 'tf-modules-vpc-endpoint-v1.0.0'), + ).toBe(true); + expect( + TerraformModule.isModuleAssociatedWithTag('tf-modules/vpc-endpoint', 'tf-modules_vpc_endpoint_v1.0.0'), + ).toBe(true); + expect( + TerraformModule.isModuleAssociatedWithTag('tf-modules/vpc-endpoint', 'tf-modules.vpc.endpoint.v1.0.0'), ).toBe(true); - expect(TerraformModule.isModuleAssociatedWithTag('tf-modules/vpc-endpoint', 'tf-modules/vpc/v1.0.0')).toBe( - false, - ); }); it('should be case sensitive', () => { expect(TerraformModule.isModuleAssociatedWithTag('my-module', 'My-Module/v1.0.0')).toBe(false); expect(TerraformModule.isModuleAssociatedWithTag('my-module', 'my-module/V1.0.0')).toBe(false); }); + + it('should handle tags with different directory separators', () => { + // Test that tags with different separators are properly associated after normalization + expect(TerraformModule.isModuleAssociatedWithTag('my-module', 'my-module/v1.0.0')).toBe(true); + expect(TerraformModule.isModuleAssociatedWithTag('my-module', 'my-module-v1.0.0')).toBe(true); + expect(TerraformModule.isModuleAssociatedWithTag('my-module', 'my-module_v1.0.0')).toBe(true); + expect(TerraformModule.isModuleAssociatedWithTag('my-module', 'my-module.v1.0.0')).toBe(true); + + // Test complex module names with separators + expect( + TerraformModule.isModuleAssociatedWithTag('tf-modules/vpc-endpoint', 'tf-modules/vpc-endpoint/v1.0.0'), + ).toBe(true); + expect( + TerraformModule.isModuleAssociatedWithTag('tf-modules/vpc-endpoint', 'tf-modules-vpc-endpoint-v1.0.0'), + ).toBe(true); + expect( + TerraformModule.isModuleAssociatedWithTag('tf-modules/vpc-endpoint', 'tf-modules_vpc_endpoint_v1.0.0'), + ).toBe(true); + expect( + TerraformModule.isModuleAssociatedWithTag('tf-modules/vpc-endpoint', 'tf-modules.vpc.endpoint.v1.0.0'), + ).toBe(true); + + // Test that wrong associations still return false + expect(TerraformModule.isModuleAssociatedWithTag('my-module', 'other-module/v1.0.0')).toBe(false); + expect(TerraformModule.isModuleAssociatedWithTag('my-module', 'other-module-v1.0.0')).toBe(false); + expect(TerraformModule.isModuleAssociatedWithTag('my-module', 'other-module_v1.0.0')).toBe(false); + expect(TerraformModule.isModuleAssociatedWithTag('my-module', 'other-module.v1.0.0')).toBe(false); + }); }); describe('getTagsForModule()', () => { @@ -1147,6 +1420,45 @@ describe('TerraformModule', () => { expect(tagsToDelete).toEqual(['apple-module/v1.0.0', 'banana-module/v1.0.0', 'zebra-module/v1.0.0']); }); + + it('should handle tags with different directory separators for same module', () => { + // Scenario: Module was originally using / separator, but tags exist with various separators + const allTags = [ + 'test-module/v1.0.0', // Forward slash (current format) + 'test-module-v1.1.0', // Hyphen (old format) + 'test-module_v1.2.0', // Underscore (old format) + 'test-module.v1.3.0', // Dot (old format) + 'other-module/v1.0.0', // Different module that no longer exists + ]; + const existingModules = [createMockTerraformModule({ directory: join(tmpDir, 'test-module') })]; + + const tagsToDelete = TerraformModule.getTagsToDelete(allTags, existingModules); + + // Only tags for non-existent modules should be deleted + // All test-module tags should be kept regardless of separator + expect(tagsToDelete).toEqual(['other-module/v1.0.0']); + }); + + it('should handle complex module names with various separators', () => { + const allTags = [ + 'tf-modules/vpc-endpoint/v1.0.0', // Current format + 'tf-modules-vpc-endpoint-v1.1.0', // All hyphens + 'tf-modules_vpc_endpoint_v1.2.0', // All underscores + 'tf-modules.vpc.endpoint.v1.3.0', // All dots + 'tf-modules/vpc-endpoint-v1.4.0', // Mixed separators + 'removed-module/v1.0.0', // Module that no longer exists + ]; + const existingModules = [ + createMockTerraformModule({ + directory: join(tmpDir, 'tf-modules', 'vpc-endpoint'), + }), + ]; + + const tagsToDelete = TerraformModule.getTagsToDelete(allTags, existingModules); + + // Only tags for non-existent modules should be deleted + expect(tagsToDelete).toEqual(['removed-module/v1.0.0']); + }); }); describe('getReleasesToDelete()', () => { @@ -1536,6 +1848,102 @@ describe('TerraformModule', () => { expect(releasesToDelete).toHaveLength(2); expect(releasesToDelete.map((r) => r.tagName)).toEqual(['legacy-module/v1.0.0', 'legacy-module/v1.2.0']); }); + + it('should handle releases with different directory separators for same module', () => { + // Scenario: Module was originally using / separator, but releases exist with various separators + const allReleases: GitHubRelease[] = [ + { + id: 1, + title: 'test-module/v1.0.0', + tagName: 'test-module/v1.0.0', + body: 'Forward slash format', + }, + { + id: 2, + title: 'test-module-v1.1.0', + tagName: 'test-module-v1.1.0', + body: 'Hyphen format', + }, + { + id: 3, + title: 'test-module_v1.2.0', + tagName: 'test-module_v1.2.0', + body: 'Underscore format', + }, + { + id: 4, + title: 'test-module.v1.3.0', + tagName: 'test-module.v1.3.0', + body: 'Dot format', + }, + { + id: 5, + title: 'other-module/v1.0.0', + tagName: 'other-module/v1.0.0', + body: 'Different module that no longer exists', + }, + ]; + const existingModules = [createMockTerraformModule({ directory: join(tmpDir, 'test-module') })]; + + const releasesToDelete = TerraformModule.getReleasesToDelete(allReleases, existingModules); + + // Only releases for non-existent modules should be deleted + // All test-module releases should be kept regardless of separator + expect(releasesToDelete).toHaveLength(1); + expect(releasesToDelete[0].tagName).toBe('other-module/v1.0.0'); + }); + + it('should handle complex module names with various separators in releases', () => { + const allReleases: GitHubRelease[] = [ + { + id: 1, + title: 'tf-modules/vpc-endpoint/v1.0.0', + tagName: 'tf-modules/vpc-endpoint/v1.0.0', + body: 'Current format', + }, + { + id: 2, + title: 'tf-modules-vpc-endpoint-v1.1.0', + tagName: 'tf-modules-vpc-endpoint-v1.1.0', + body: 'All hyphens', + }, + { + id: 3, + title: 'tf-modules_vpc_endpoint_v1.2.0', + tagName: 'tf-modules_vpc_endpoint_v1.2.0', + body: 'All underscores', + }, + { + id: 4, + title: 'tf-modules.vpc.endpoint.v1.3.0', + tagName: 'tf-modules.vpc.endpoint.v1.3.0', + body: 'All dots', + }, + { + id: 5, + title: 'tf-modules/vpc-endpoint-v1.4.0', + tagName: 'tf-modules/vpc-endpoint-v1.4.0', + body: 'Mixed separators', + }, + { + id: 6, + title: 'removed-module/v1.0.0', + tagName: 'removed-module/v1.0.0', + body: 'Module that no longer exists', + }, + ]; + const existingModules = [ + createMockTerraformModule({ + directory: join(tmpDir, 'tf-modules', 'vpc-endpoint'), + }), + ]; + + const releasesToDelete = TerraformModule.getReleasesToDelete(allReleases, existingModules); + + // Only releases for non-existent modules should be deleted + expect(releasesToDelete).toHaveLength(1); + expect(releasesToDelete[0].tagName).toBe('removed-module/v1.0.0'); + }); }); // Test private helper methods via the public interface diff --git a/__tests__/utils/constants.test.ts b/__tests__/utils/constants.test.ts index af782714..abbde3bd 100644 --- a/__tests__/utils/constants.test.ts +++ b/__tests__/utils/constants.test.ts @@ -1,4 +1,7 @@ import { + VALID_TAG_DIRECTORY_SEPARATORS, + VERSION_TAG_REGEX, + MODULE_TAG_REGEX, BRANDING_COMMENT, BRANDING_WIKI, GITHUB_ACTIONS_BOT_NAME, @@ -11,6 +14,18 @@ import { import { describe, expect, it } from 'vitest'; describe('utils/constants', () => { + it('should have the correct default separators', () => { + expect(VALID_TAG_DIRECTORY_SEPARATORS).toStrictEqual(['-', '_', '/', '.']); + }); + + it('should have the correct version tag regex', () => { + expect(VERSION_TAG_REGEX).toStrictEqual(/^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/); + }); + + it('should have the correct module tag regex', () => { + expect(MODULE_TAG_REGEX).toStrictEqual(/^(.+)([-_/.])(v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*))$/); + }); + it('should have the correct GitHub Actions bot name', () => { expect(GITHUB_ACTIONS_BOT_NAME).toBe('GitHub Actions'); }); diff --git a/__tests__/utils/file.test.ts b/__tests__/utils/file.test.ts index 558f46ff..989a42a2 100644 --- a/__tests__/utils/file.test.ts +++ b/__tests__/utils/file.test.ts @@ -619,7 +619,7 @@ describe('utils/file', () => { // Create terraform file above workspace (if possible) try { writeFileSync(terraformFileAbove, 'resource "test" "example" {}'); - } catch (error) { + } catch (_error) { // Skip this test if we can't write above tmpDir return; } @@ -635,7 +635,7 @@ describe('utils/file', () => { // Cleanup try { rmSync(terraformFileAbove); - } catch (error) { + } catch (_error) { // Ignore cleanup errors } }); diff --git a/__tests__/utils/metadata.test.ts b/__tests__/utils/metadata.test.ts new file mode 100644 index 00000000..e4847a99 --- /dev/null +++ b/__tests__/utils/metadata.test.ts @@ -0,0 +1,172 @@ +import { ACTION_INPUTS, createConfigFromInputs } from '@/utils/metadata'; +import type { ActionInputMetadata } from '@/types'; +import { getInput } from '@actions/core'; +import { describe, expect, it, vi } from 'vitest'; + +describe('utils/metadata', () => { + describe('ACTION_INPUTS', () => { + it('should contain all expected input configurations', () => { + const expectedInputs = [ + 'major-keywords', + 'minor-keywords', + 'patch-keywords', + 'default-first-tag', + 'terraform-docs-version', + 'delete-legacy-tags', + 'disable-wiki', + 'wiki-sidebar-changelog-max', + 'wiki-usage-template', + 'disable-branding', + 'module-path-ignore', + 'module-change-exclude-patterns', + 'module-asset-exclude-patterns', + 'use-ssh-source-format', + 'github_token', + 'tag-directory-separator', + 'use-version-prefix', + ]; + + expect(Object.keys(ACTION_INPUTS)).toEqual(expect.arrayContaining(expectedInputs)); + expect(Object.keys(ACTION_INPUTS)).toHaveLength(expectedInputs.length); + }); + + it('should have correct metadata structure for required string inputs', () => { + const stringInputs = ['default-first-tag', 'terraform-docs-version', 'github_token', 'tag-directory-separator']; + + for (const inputName of stringInputs) { + const metadata = ACTION_INPUTS[inputName]; + expect(metadata).toEqual({ + configKey: expect.any(String), + required: true, + type: 'string', + }); + } + }); + + it('should have correct metadata structure for required boolean inputs', () => { + const booleanInputs = [ + 'delete-legacy-tags', + 'disable-wiki', + 'disable-branding', + 'use-ssh-source-format', + 'use-version-prefix', + ]; + + for (const inputName of booleanInputs) { + const metadata = ACTION_INPUTS[inputName]; + expect(metadata).toEqual({ + configKey: expect.any(String), + required: true, + type: 'boolean', + }); + } + }); + + it('should have correct metadata structure for required array inputs', () => { + const arrayInputs = ['major-keywords', 'minor-keywords', 'patch-keywords']; + + for (const inputName of arrayInputs) { + const metadata = ACTION_INPUTS[inputName]; + expect(metadata).toEqual({ + configKey: expect.any(String), + required: true, + type: 'array', + }); + } + }); + + it('should have correct metadata structure for required number inputs', () => { + const numberInputs = ['wiki-sidebar-changelog-max']; + + for (const inputName of numberInputs) { + const metadata = ACTION_INPUTS[inputName]; + expect(metadata).toEqual({ + configKey: expect.any(String), + required: true, + type: 'number', + }); + } + }); + + it('should have correct metadata structure for optional array inputs', () => { + const optionalArrayInputs = [ + 'module-path-ignore', + 'module-change-exclude-patterns', + 'module-asset-exclude-patterns', + ]; + + for (const inputName of optionalArrayInputs) { + const metadata = ACTION_INPUTS[inputName]; + expect(metadata).toEqual({ + configKey: expect.any(String), + required: false, + type: 'array', + }); + } + }); + + it('should have proper configKey mappings', () => { + const expectedMappings: Record = { + 'major-keywords': 'majorKeywords', + 'minor-keywords': 'minorKeywords', + 'patch-keywords': 'patchKeywords', + 'default-first-tag': 'defaultFirstTag', + 'terraform-docs-version': 'terraformDocsVersion', + 'delete-legacy-tags': 'deleteLegacyTags', + 'disable-wiki': 'disableWiki', + 'wiki-sidebar-changelog-max': 'wikiSidebarChangelogMax', + 'disable-branding': 'disableBranding', + 'module-path-ignore': 'modulePathIgnore', + 'module-change-exclude-patterns': 'moduleChangeExcludePatterns', + 'module-asset-exclude-patterns': 'moduleAssetExcludePatterns', + 'use-ssh-source-format': 'useSSHSourceFormat', + github_token: 'githubToken', + 'tag-directory-separator': 'tagDirectorySeparator', + 'use-version-prefix': 'useVersionPrefix', + }; + + for (const [inputName, expectedConfigKey] of Object.entries(expectedMappings)) { + expect(ACTION_INPUTS[inputName].configKey).toBe(expectedConfigKey); + } + }); + + it('should maintain type safety with ActionInputMetadata interface', () => { + // This test ensures the factory functions create valid ActionInputMetadata objects + for (const metadata of Object.values(ACTION_INPUTS)) { + expect(metadata).toEqual( + expect.objectContaining({ + configKey: expect.any(String), + required: expect.any(Boolean), + type: expect.stringMatching(/^(string|boolean|array|number)$/), + }), + ); + + // Ensure type is properly typed + const validTypes: ActionInputMetadata['type'][] = ['string', 'boolean', 'array', 'number']; + expect(validTypes).toContain(metadata.type); + } + }); + }); + + describe('createConfigFromInputs', () => { + it('should throw a custom error if getInput fails', () => { + const errorMessage = 'Input retrieval failed'; + vi.mocked(getInput).mockImplementation(() => { + throw new Error(errorMessage); + }); + + expect(() => createConfigFromInputs()).toThrow(`Failed to process input 'major-keywords': ${errorMessage}`); + }); + + it('should handle non-Error objects thrown during input processing', () => { + const errorObject = 'A plain string error'; + vi.mocked(getInput).mockImplementation(() => { + throw errorObject; + }); + + expect(() => createConfigFromInputs()).toThrow( + `Failed to process input 'major-keywords': ${String(errorObject)}`, + ); + }); + }); +}); diff --git a/__tests__/utils/string.test.ts b/__tests__/utils/string.test.ts index b2ce45d7..6d95e89a 100644 --- a/__tests__/utils/string.test.ts +++ b/__tests__/utils/string.test.ts @@ -1,36 +1,49 @@ -import { removeTrailingCharacters, trimSlashes } from '@/utils/string'; +import { removeLeadingCharacters, removeTrailingCharacters, renderTemplate } from '@/utils/string'; import { describe, expect, it } from 'vitest'; describe('utils/string', () => { - describe('trimSlashes', () => { - it('should remove leading and trailing slashes while preserving internal ones', () => { - const testCases = [ - { input: '/example/path/', expected: 'example/path' }, - { input: '///another/example///', expected: 'another/example' }, - { input: 'no/slashes', expected: 'no/slashes' }, - { input: '/', expected: '' }, - { input: '//', expected: '' }, - { input: '', expected: '' }, - { input: '/single/', expected: 'single' }, - { input: 'leading/', expected: 'leading' }, - { input: '/trailing', expected: 'trailing' }, - { input: '////multiple////slashes////', expected: 'multiple////slashes' }, - ]; - for (const { input, expected } of testCases) { - expect(trimSlashes(input)).toBe(expected); - } + describe('removeLeadingCharacters', () => { + it('should remove leading dots', () => { + expect(removeLeadingCharacters('...hello', ['.'])).toBe('hello'); + expect(removeLeadingCharacters('..module-name', ['.'])).toBe('module-name'); + expect(removeLeadingCharacters('.....test', ['.'])).toBe('test'); }); - it('should handle strings without any slashes', () => { - expect(trimSlashes('hello')).toBe('hello'); + it('should remove leading hyphens and underscores', () => { + expect(removeLeadingCharacters('--module-name', ['-'])).toBe('module-name'); + expect(removeLeadingCharacters('__module_name', ['_'])).toBe('module_name'); + expect(removeLeadingCharacters('-_module-name', ['-', '_'])).toBe('module-name'); }); - it('should return empty string when given only slashes', () => { - expect(trimSlashes('//////')).toBe(''); + it('should remove multiple leading character types', () => { + expect(removeLeadingCharacters('._-module-name', ['.', '-', '_'])).toBe('module-name'); + expect(removeLeadingCharacters('.--__test', ['.', '-', '_'])).toBe('test'); + expect(removeLeadingCharacters('___...---example', ['.', '-', '_'])).toBe('example'); }); - it('should preserve internal multiple slashes', () => { - expect(trimSlashes('/path//with///internal////slashes/')).toBe('path//with///internal////slashes'); + it('should preserve internal characters', () => { + expect(removeLeadingCharacters('.hello.world', ['.'])).toBe('hello.world'); + expect(removeLeadingCharacters('.-module-name.test', ['.', '-'])).toBe('module-name.test'); + expect(removeLeadingCharacters('_test_module_name', ['_'])).toBe('test_module_name'); + }); + + it('should handle edge cases', () => { + expect(removeLeadingCharacters('', ['.'])).toBe(''); + expect(removeLeadingCharacters('...', ['.'])).toBe(''); + expect(removeLeadingCharacters('---', ['-'])).toBe(''); + expect(removeLeadingCharacters('hello', ['.', '-', '_'])).toBe('hello'); + expect(removeLeadingCharacters('module', [])).toBe('module'); + }); + + it('should handle complex terraform module names', () => { + expect(removeLeadingCharacters('._-aws-vpc-module', ['.', '-', '_'])).toBe('aws-vpc-module'); + expect(removeLeadingCharacters('--tf-modules/vpc-endpoint', ['-', '_'])).toBe('tf-modules/vpc-endpoint'); + expect(removeLeadingCharacters('__modules/networking/vpc', ['_'])).toBe('modules/networking/vpc'); + }); + + it('should handle forward slashes in leading characters', () => { + expect(removeLeadingCharacters('/./module-name', ['/', '.'])).toBe('module-name'); + expect(removeLeadingCharacters('/./_-example', ['/', '.', '_', '-'])).toBe('example'); }); }); @@ -72,5 +85,96 @@ describe('utils/string', () => { expect(removeTrailingCharacters('tf-modules/vpc-endpoint--', ['-', '_'])).toBe('tf-modules/vpc-endpoint'); expect(removeTrailingCharacters('modules/networking/vpc__', ['_'])).toBe('modules/networking/vpc'); }); + + it('should handle forward slashes in trailing characters', () => { + expect(removeTrailingCharacters('module-name/.', ['/', '.'])).toBe('module-name'); + expect(removeTrailingCharacters('example-_./', ['/', '.', '_', '-'])).toBe('example'); + }); + }); + + describe('renderTemplate', () => { + it('should replace a single placeholder', () => { + const template = 'Hello, {{name}}!'; + const variables = { name: 'World' }; + const result = renderTemplate(template, variables); + expect(result).toBe('Hello, World!'); + }); + + it('should replace multiple placeholders', () => { + const template = '{{greeting}}, {{name}}!'; + const variables = { greeting: 'Hi', name: 'There' }; + const result = renderTemplate(template, variables); + expect(result).toBe('Hi, There!'); + }); + + it('should handle templates with no placeholders', () => { + const template = 'Just a plain string.'; + const variables = { name: 'World' }; + const result = renderTemplate(template, variables); + expect(result).toBe('Just a plain string.'); + }); + + it('should handle empty string values', () => { + const template = 'A{{key}}B'; + const variables = { key: '' }; + const result = renderTemplate(template, variables); + expect(result).toBe('AB'); + }); + + it('should leave unmapped placeholders untouched', () => { + const template = 'Hello, {{name}} and {{unmapped}}!'; + const variables = { name: 'World' }; + const result = renderTemplate(template, variables); + expect(result).toBe('Hello, World and {{unmapped}}!'); + }); + + it('should handle complex templates with multiple variables', () => { + const template = 'Module: {{module}}, Version: {{version}}, Author: {{author}}'; + const variables = { module: 'vpc-endpoint', version: '1.0.0', author: 'TechPivot' }; + const result = renderTemplate(template, variables); + expect(result).toBe('Module: vpc-endpoint, Version: 1.0.0, Author: TechPivot'); + }); + + it('should handle numeric values as strings', () => { + const template = 'Port: {{port}}, Count: {{count}}'; + const variables = { port: '8080', count: '3' }; + const result = renderTemplate(template, variables); + expect(result).toBe('Port: 8080, Count: 3'); + }); + + it('should handle special characters in values', () => { + const template = 'Path: {{path}}, Command: {{cmd}}'; + const variables = { path: '/opt/bin/terraform', cmd: 'terraform init -backend=false' }; + const result = renderTemplate(template, variables); + expect(result).toBe('Path: /opt/bin/terraform, Command: terraform init -backend=false'); + }); + + it('should handle empty template', () => { + const template = ''; + const variables = { name: 'World' }; + const result = renderTemplate(template, variables); + expect(result).toBe(''); + }); + + it('should handle empty variables object', () => { + const template = 'Hello, {{name}}!'; + const variables = {}; + const result = renderTemplate(template, variables); + expect(result).toBe('Hello, {{name}}!'); + }); + + it('should handle placeholders with different casing', () => { + const template = 'Hello, {{Name}} and {{NAME}}!'; + const variables = { Name: 'World', NAME: 'UNIVERSE' }; + const result = renderTemplate(template, variables); + expect(result).toBe('Hello, World and UNIVERSE!'); + }); + + it('should handle placeholders with numbers', () => { + const template = 'Item {{item1}} and {{item2}}'; + const variables = { item1: 'first', item2: 'second' }; + const result = renderTemplate(template, variables); + expect(result).toBe('Item first and second'); + }); }); }); diff --git a/__tests__/wiki.test.ts b/__tests__/wiki.test.ts index 2b003ba4..be0fdc0e 100644 --- a/__tests__/wiki.test.ts +++ b/__tests__/wiki.test.ts @@ -103,7 +103,7 @@ describe('wiki', async () => { it('should handle unsetting config extraheader and throwing error accordingly', () => { const mockExecFileSync = vi.fn( - (command: string, args?: readonly string[] | undefined, options?: ExecFileSyncOptions) => { + (_command: string, args?: readonly string[] | undefined, _options?: ExecFileSyncOptions) => { if (args?.includes('--unset-all') && args.includes('http.https://github.com/.extraheader')) { const error = new Error('git config error') as ExecSyncError; error.status = 10; @@ -131,7 +131,7 @@ describe('wiki', async () => { it('should handle unsetting config extraheader gracefully', () => { const mockExecFileSync = vi.fn( - (command: string, args?: readonly string[] | undefined, options?: ExecFileSyncOptions) => { + (_command: string, args?: readonly string[] | undefined, _options?: ExecFileSyncOptions) => { if (args?.includes('--unset-all') && args.includes('http.https://github.com/.extraheader')) { const error = new Error('git config error') as ExecSyncError; error.status = 5; @@ -168,7 +168,7 @@ describe('wiki', async () => { // Reset mocks and configure remote command to return "origin" vi.clearAllMocks(); - vi.mocked(execFileSync).mockImplementation((cmd, args = []) => { + vi.mocked(execFileSync).mockImplementation((_cmd, args = []) => { if (args[0] === 'remote') { return Buffer.from('origin'); } @@ -305,7 +305,7 @@ describe('wiki', async () => { basename(file) !== '_Footer.md' ) { const content = readFileSync(file, 'utf8'); - const moduleName = basename(file, '.md'); + const _moduleName = basename(file, '.md'); expect(content).toContain(`# Usage\n\nModule: ${terraformModule.name}, Missing: {{missing_variable}}`); } } diff --git a/action.yml b/action.yml index 958186ba..c51c135b 100644 --- a/action.yml +++ b/action.yml @@ -51,6 +51,19 @@ inputs: Adjust this value to control the visibility of changelog entries in the module sidebar. required: true default: "5" + wiki-usage-template: + description: A raw, multi-line string to override the default 'Usage' section in the generated wiki. If not provided, a default usage block will be generated. + required: false + default: | + To use this module in your Terraform, refer to the below module example: + + ```hcl + module "{{module_name_terraform}}" { + source = "git::{{module_source}}?ref={{latest_tag}}" + + # See inputs below for additional required parameters + } + ``` disable-branding: description: > Flag to control whether the small branding link should be disabled or not in the @@ -102,19 +115,6 @@ inputs: If enabled, all links to source code in generated Wiki documentation will use SSH format instead of HTTPS format. required: true default: "false" - wiki-usage-template: - description: A raw, multi-line string to override the default 'Usage' section in the generated wiki. If not provided, a default usage block will be generated. - required: false - default: | - To use this module in your Terraform, refer to the below module example: - - ```hcl - module "{{module_name_terraform}}" { - source = "git::{{module_source}}?ref={{latest_tag}}" - - # See inputs below for additional required parameters - } - ``` github_token: description: > Required for retrieving pull request metadata, tags, releases, updating PR comments, wiki, and creating @@ -122,6 +122,27 @@ inputs: specific requirements. required: true default: ${{ github.token }} + tag-directory-separator: + description: > + Character used to separate directory path components in Git tags. This separator is used to convert + module directory paths into tag names (e.g., 'modules/aws/s3-bucket' becomes 'modules-aws-s3-bucket-v1.0.0' + when using '-'). Must be a single character from: /, -, _, or . + + Examples with different separators: + - "/" (default): modules/aws/s3-bucket/v1.0.0 + - "-": modules-aws-s3-bucket-v1.0.0 + - "_": modules_aws_s3_bucket_v1.0.0 + - ".": modules.aws.s3.bucket.v1.0.0 + required: true + default: / + use-version-prefix: + description: > + Whether to include the 'v' prefix on version tags (e.g., v1.2.3 vs 1.2.3). When enabled, all new version + tags will include the 'v' prefix. For initial releases, this setting takes precedence over any 'v' prefix + specified in the default-first-tag - if use-version-prefix is false and default-first-tag contains 'v', + the 'v' will be automatically removed to ensure consistency. + required: true + default: "true" outputs: changed-module-names: diff --git a/biome.json b/biome.json index 3d7ea7c8..7ed801a8 100644 --- a/biome.json +++ b/biome.json @@ -1,12 +1,15 @@ { - "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json", + "$schema": "https://biomejs.dev/schemas/2.2.0/schema.json", "vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true }, + "assist": { + "enabled": false + }, "files": { - "ignore": ["dist", "node_modules"] + "includes": ["*.md", "*.ts", "__mocks__/**/*.ts", "__tests__/**/*.ts", "src/**/*.ts", "scripts/**/*.ts"] }, "formatter": { "enabled": true, @@ -24,9 +27,6 @@ } } }, - "organizeImports": { - "enabled": true - }, "javascript": { "formatter": { "enabled": true, diff --git a/package-lock.json b/package-lock.json index 290b2ee1..e146f53d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,20 +15,22 @@ "@octokit/plugin-rest-endpoint-methods": "^16.0.0", "@octokit/request-error": "^7.0.0", "minimatch": "^10.0.1", - "p-limit": "^6.2.0", + "p-limit": "^7.0.0", "which": "^5.0.0" }, "devDependencies": { - "@biomejs/biome": "^1.9.4", + "@biomejs/biome": "^2.2.0", "@octokit/types": "^14.0.0", "@octokit/webhooks-types": "^7.6.1", - "@types/node": "^22.15.29", + "@types/js-yaml": "^4.0.9", + "@types/node": "^22.17.2", "@types/which": "^3.0.4", "@vercel/ncc": "^0.38.3", "@vitest/coverage-v8": "^3.1.3", + "js-yaml": "^4.1.0", "make-coverage-badge": "^1.2.0", - "openai": "*", - "textlint": "^14.8.0", + "openai": "latest", + "textlint": "^15.2.1", "textlint-filter-rule-comments": "^1.2.2", "textlint-rule-terminology": "^5.2.12", "ts-deepmerge": "^7.0.2", @@ -106,6 +108,28 @@ "@azu/format-text": "^1.0.1" } }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -127,13 +151,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", - "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.3" + "@babel/types": "^7.28.2" }, "bin": { "parser": "bin/babel-parser.js" @@ -143,9 +167,9 @@ } }, "node_modules/@babel/types": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", - "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "dev": true, "license": "MIT", "dependencies": { @@ -167,11 +191,10 @@ } }, "node_modules/@biomejs/biome": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz", - "integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.2.0.tgz", + "integrity": "sha512-3On3RSYLsX+n9KnoSgfoYlckYBoU6VRM22cw1gB4Y0OuUVSYd/O/2saOJMrA4HFfA1Ff0eacOvMN1yAAvHtzIw==", "dev": true, - "hasInstallScript": true, "license": "MIT OR Apache-2.0", "bin": { "biome": "bin/biome" @@ -184,20 +207,20 @@ "url": "https://opencollective.com/biome" }, "optionalDependencies": { - "@biomejs/cli-darwin-arm64": "1.9.4", - "@biomejs/cli-darwin-x64": "1.9.4", - "@biomejs/cli-linux-arm64": "1.9.4", - "@biomejs/cli-linux-arm64-musl": "1.9.4", - "@biomejs/cli-linux-x64": "1.9.4", - "@biomejs/cli-linux-x64-musl": "1.9.4", - "@biomejs/cli-win32-arm64": "1.9.4", - "@biomejs/cli-win32-x64": "1.9.4" + "@biomejs/cli-darwin-arm64": "2.2.0", + "@biomejs/cli-darwin-x64": "2.2.0", + "@biomejs/cli-linux-arm64": "2.2.0", + "@biomejs/cli-linux-arm64-musl": "2.2.0", + "@biomejs/cli-linux-x64": "2.2.0", + "@biomejs/cli-linux-x64-musl": "2.2.0", + "@biomejs/cli-win32-arm64": "2.2.0", + "@biomejs/cli-win32-x64": "2.2.0" } }, "node_modules/@biomejs/cli-darwin-arm64": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz", - "integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.2.0.tgz", + "integrity": "sha512-zKbwUUh+9uFmWfS8IFxmVD6XwqFcENjZvEyfOxHs1epjdH3wyyMQG80FGDsmauPwS2r5kXdEM0v/+dTIA9FXAg==", "cpu": [ "arm64" ], @@ -212,9 +235,9 @@ } }, "node_modules/@biomejs/cli-darwin-x64": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz", - "integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.2.0.tgz", + "integrity": "sha512-+OmT4dsX2eTfhD5crUOPw3RPhaR+SKVspvGVmSdZ9y9O/AgL8pla6T4hOn1q+VAFBHuHhsdxDRJgFCSC7RaMOw==", "cpu": [ "x64" ], @@ -229,9 +252,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz", - "integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.2.0.tgz", + "integrity": "sha512-6eoRdF2yW5FnW9Lpeivh7Mayhq0KDdaDMYOJnH9aT02KuSIX5V1HmWJCQQPwIQbhDh68Zrcpl8inRlTEan0SXw==", "cpu": [ "arm64" ], @@ -246,9 +269,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64-musl": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz", - "integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.2.0.tgz", + "integrity": "sha512-egKpOa+4FL9YO+SMUMLUvf543cprjevNc3CAgDNFLcjknuNMcZ0GLJYa3EGTCR2xIkIUJDVneBV3O9OcIlCEZQ==", "cpu": [ "arm64" ], @@ -263,9 +286,9 @@ } }, "node_modules/@biomejs/cli-linux-x64": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz", - "integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.2.0.tgz", + "integrity": "sha512-5UmQx/OZAfJfi25zAnAGHUMuOd+LOsliIt119x2soA2gLggQYrVPA+2kMUxR6Mw5M1deUF/AWWP2qpxgH7Nyfw==", "cpu": [ "x64" ], @@ -280,9 +303,9 @@ } }, "node_modules/@biomejs/cli-linux-x64-musl": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz", - "integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.2.0.tgz", + "integrity": "sha512-I5J85yWwUWpgJyC1CcytNSGusu2p9HjDnOPAFG4Y515hwRD0jpR9sT9/T1cKHtuCvEQ/sBvx+6zhz9l9wEJGAg==", "cpu": [ "x64" ], @@ -297,9 +320,9 @@ } }, "node_modules/@biomejs/cli-win32-arm64": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz", - "integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.2.0.tgz", + "integrity": "sha512-n9a1/f2CwIDmNMNkFs+JI0ZjFnMO0jdOyGNtihgUNFnlmd84yIYY2KMTBmMV58ZlVHjgmY5Y6E1hVTnSRieggA==", "cpu": [ "arm64" ], @@ -314,9 +337,9 @@ } }, "node_modules/@biomejs/cli-win32-x64": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz", - "integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.2.0.tgz", + "integrity": "sha512-Nawu5nHjP/zPKTIryh2AavzTc/KEg4um/MxWdXW0A6P/RZOyIpa7+QSjeXwAwX/utJGaCoXRPWtF3m5U/bB3Ww==", "cpu": [ "x64" ], @@ -331,9 +354,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", - "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", "cpu": [ "ppc64" ], @@ -348,9 +371,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", - "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", "cpu": [ "arm" ], @@ -365,9 +388,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", - "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", "cpu": [ "arm64" ], @@ -382,9 +405,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", - "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", "cpu": [ "x64" ], @@ -399,9 +422,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", - "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", "cpu": [ "arm64" ], @@ -416,9 +439,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", - "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", "cpu": [ "x64" ], @@ -433,9 +456,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", - "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", "cpu": [ "arm64" ], @@ -450,9 +473,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", - "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", "cpu": [ "x64" ], @@ -467,9 +490,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", - "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", "cpu": [ "arm" ], @@ -484,9 +507,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", - "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", "cpu": [ "arm64" ], @@ -501,9 +524,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", - "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", "cpu": [ "ia32" ], @@ -518,9 +541,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", - "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", "cpu": [ "loong64" ], @@ -535,9 +558,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", - "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", "cpu": [ "mips64el" ], @@ -552,9 +575,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", - "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", "cpu": [ "ppc64" ], @@ -569,9 +592,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", - "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", "cpu": [ "riscv64" ], @@ -586,9 +609,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", - "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", "cpu": [ "s390x" ], @@ -603,9 +626,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", - "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", "cpu": [ "x64" ], @@ -620,9 +643,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", - "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", "cpu": [ "arm64" ], @@ -637,9 +660,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", - "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", "cpu": [ "x64" ], @@ -654,9 +677,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", - "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", "cpu": [ "arm64" ], @@ -671,9 +694,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", - "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", "cpu": [ "x64" ], @@ -687,10 +710,27 @@ "node": ">=18" } }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", - "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", "cpu": [ "x64" ], @@ -705,9 +745,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", - "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", "cpu": [ "arm64" ], @@ -722,9 +762,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", - "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", "cpu": [ "ia32" ], @@ -739,9 +779,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", - "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", "cpu": [ "x64" ], @@ -764,6 +804,27 @@ "node": ">=14" } }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -793,18 +854,14 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -817,27 +874,17 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "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/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -846,19 +893,16 @@ } }, "node_modules/@keyv/serialize": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.0.3.tgz", - "integrity": "sha512-qnEovoOp5Np2JDGonIDL6Ayihw0RhnRh6vxPuHo4RDn1UOzwEo4AeIfpL6UGIrsceWrCMiVPgwRjbHu4vYFc3g==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.0.tgz", + "integrity": "sha512-RlDgexML7Z63Q8BSaqhXdCYNBy/JQnqYIwxofUrNLGCblOMHp+xux2Q8nLMLlPpgHQPoU0Do8Z6btCpRBEqZ8g==", "dev": true, - "license": "MIT", - "dependencies": { - "buffer": "^6.0.3" - } + "license": "MIT" }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.12.1.tgz", - "integrity": "sha512-KG1CZhZfWg+u8pxeM/mByJDScJSrjjxLc8fwQqbsS8xCjBmQfMNEBTotYdNanKekepnfRI85GtgQlctLFpcYPw==", + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.3.tgz", + "integrity": "sha512-JPwUKWSsbzx+DLFznf/QZ32Qa+ptfbUlHhRLrBQBAFu9iI1iYvizM4p+zhhRDceSsPutXp4z+R/HPVphlIiclg==", "dev": true, "license": "MIT", "dependencies": { @@ -867,6 +911,7 @@ "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", @@ -888,9 +933,9 @@ } }, "node_modules/@octokit/core": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.2.tgz", - "integrity": "sha512-ODsoD39Lq6vR6aBgvjTnA3nZGliknKboc9Gtxr7E4WDNqY24MxANKcuDQSF0jzapvGb3KWOEDrKfve4HoWGK+g==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.3.tgz", + "integrity": "sha512-oNXsh2ywth5aowwIa7RKtawnkdH6LgU1ztfP9AIUCQCvzysB+WeU8o2kyyosDPwBZutPpjZDKPQGIzzrfTWweQ==", "license": "MIT", "dependencies": { "@octokit/auth-token": "^6.0.0", @@ -939,9 +984,9 @@ "license": "MIT" }, "node_modules/@octokit/plugin-paginate-rest": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-13.0.1.tgz", - "integrity": "sha512-m1KvHlueScy4mQJWvFDCxFBTIdXS0K1SgFGLmqHyX90mZdCIv6gWBbKRhatxRjhGlONuTK/hztYdaqrTXcFZdQ==", + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-13.1.1.tgz", + "integrity": "sha512-q9iQGlZlxAVNRN2jDNskJW/Cafy7/XE52wjZ5TTvyhyOD904Cvx//DNyoO3J/MXJ0ve3rPoNWKEg5iZrisQSuw==", "license": "MIT", "dependencies": { "@octokit/types": "^14.1.0" @@ -969,9 +1014,9 @@ } }, "node_modules/@octokit/request": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.2.tgz", - "integrity": "sha512-iYj4SJG/2bbhh+iIpFmG5u49DtJ4lipQ+aPakjL9OKpsGY93wM8w06gvFbEQxcMsZcCvk5th5KkIm2m8o14aWA==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.3.tgz", + "integrity": "sha512-V6jhKokg35vk098iBqp2FBKunk3kMTXlmq+PtbV9Gl3TfskWlebSofU9uunVKhUN7xl+0+i5vt0TGTG8/p/7HA==", "license": "MIT", "dependencies": { "@octokit/endpoint": "^11.0.0", @@ -1024,9 +1069,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.42.0.tgz", - "integrity": "sha512-gldmAyS9hpj+H6LpRNlcjQWbuKUtb94lodB9uCz71Jm+7BxK1VIOo7y62tZZwxhA7j1ylv/yQz080L5WkS+LoQ==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.3.tgz", + "integrity": "sha512-UmTdvXnLlqQNOCJnyksjPs1G4GqXNGW1LrzCe8+8QoaLhhDeTXYBgJ3k6x61WIhlHX2U+VzEJ55TtIjR/HTySA==", "cpu": [ "arm" ], @@ -1038,9 +1083,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.42.0.tgz", - "integrity": "sha512-bpRipfTgmGFdCZDFLRvIkSNO1/3RGS74aWkJJTFJBH7h3MRV4UijkaEUeOMbi9wxtxYmtAbVcnMtHTPBhLEkaw==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.3.tgz", + "integrity": "sha512-8NoxqLpXm7VyeI0ocidh335D6OKT0UJ6fHdnIxf3+6oOerZZc+O7r+UhvROji6OspyPm+rrIdb1gTXtVIqn+Sg==", "cpu": [ "arm64" ], @@ -1052,9 +1097,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.42.0.tgz", - "integrity": "sha512-JxHtA081izPBVCHLKnl6GEA0w3920mlJPLh89NojpU2GsBSB6ypu4erFg/Wx1qbpUbepn0jY4dVWMGZM8gplgA==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.3.tgz", + "integrity": "sha512-csnNavqZVs1+7/hUKtgjMECsNG2cdB8F7XBHP6FfQjqhjF8rzMzb3SLyy/1BG7YSfQ+bG75Ph7DyedbUqwq1rA==", "cpu": [ "arm64" ], @@ -1066,9 +1111,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.42.0.tgz", - "integrity": "sha512-rv5UZaWVIJTDMyQ3dCEK+m0SAn6G7H3PRc2AZmExvbDvtaDc+qXkei0knQWcI3+c9tEs7iL/4I4pTQoPbNL2SA==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.3.tgz", + "integrity": "sha512-r2MXNjbuYabSIX5yQqnT8SGSQ26XQc8fmp6UhlYJd95PZJkQD1u82fWP7HqvGUf33IsOC6qsiV+vcuD4SDP6iw==", "cpu": [ "x64" ], @@ -1080,9 +1125,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.42.0.tgz", - "integrity": "sha512-fJcN4uSGPWdpVmvLuMtALUFwCHgb2XiQjuECkHT3lWLZhSQ3MBQ9pq+WoWeJq2PrNxr9rPM1Qx+IjyGj8/c6zQ==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.3.tgz", + "integrity": "sha512-uluObTmgPJDuJh9xqxyr7MV61Imq+0IvVsAlWyvxAaBSNzCcmZlhfYcRhCdMaCsy46ccZa7vtDDripgs9Jkqsw==", "cpu": [ "arm64" ], @@ -1094,9 +1139,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.42.0.tgz", - "integrity": "sha512-CziHfyzpp8hJpCVE/ZdTizw58gr+m7Y2Xq5VOuCSrZR++th2xWAz4Nqk52MoIIrV3JHtVBhbBsJcAxs6NammOQ==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.3.tgz", + "integrity": "sha512-AVJXEq9RVHQnejdbFvh1eWEoobohUYN3nqJIPI4mNTMpsyYN01VvcAClxflyk2HIxvLpRcRggpX1m9hkXkpC/A==", "cpu": [ "x64" ], @@ -1108,9 +1153,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.42.0.tgz", - "integrity": "sha512-UsQD5fyLWm2Fe5CDM7VPYAo+UC7+2Px4Y+N3AcPh/LdZu23YcuGPegQly++XEVaC8XUTFVPscl5y5Cl1twEI4A==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.3.tgz", + "integrity": "sha512-byyflM+huiwHlKi7VHLAYTKr67X199+V+mt1iRgJenAI594vcmGGddWlu6eHujmcdl6TqSNnvqaXJqZdnEWRGA==", "cpu": [ "arm" ], @@ -1122,9 +1167,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.42.0.tgz", - "integrity": "sha512-/i8NIrlgc/+4n1lnoWl1zgH7Uo0XK5xK3EDqVTf38KvyYgCU/Rm04+o1VvvzJZnVS5/cWSd07owkzcVasgfIkQ==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.3.tgz", + "integrity": "sha512-aLm3NMIjr4Y9LklrH5cu7yybBqoVCdr4Nvnm8WB7PKCn34fMCGypVNpGK0JQWdPAzR/FnoEoFtlRqZbBBLhVoQ==", "cpu": [ "arm" ], @@ -1136,9 +1181,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.42.0.tgz", - "integrity": "sha512-eoujJFOvoIBjZEi9hJnXAbWg+Vo1Ov8n/0IKZZcPZ7JhBzxh2A+2NFyeMZIRkY9iwBvSjloKgcvnjTbGKHE44Q==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.3.tgz", + "integrity": "sha512-VtilE6eznJRDIoFOzaagQodUksTEfLIsvXymS+UdJiSXrPW7Ai+WG4uapAc3F7Hgs791TwdGh4xyOzbuzIZrnw==", "cpu": [ "arm64" ], @@ -1150,9 +1195,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.42.0.tgz", - "integrity": "sha512-/3NrcOWFSR7RQUQIuZQChLND36aTU9IYE4j+TB40VU78S+RA0IiqHR30oSh6P1S9f9/wVOenHQnacs/Byb824g==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.3.tgz", + "integrity": "sha512-dG3JuS6+cRAL0GQ925Vppafi0qwZnkHdPeuZIxIPXqkCLP02l7ka+OCyBoDEv8S+nKHxfjvjW4OZ7hTdHkx8/w==", "cpu": [ "arm64" ], @@ -1164,9 +1209,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.42.0.tgz", - "integrity": "sha512-O8AplvIeavK5ABmZlKBq9/STdZlnQo7Sle0LLhVA7QT+CiGpNVe197/t8Aph9bhJqbDVGCHpY2i7QyfEDDStDg==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.3.tgz", + "integrity": "sha512-iU8DxnxEKJptf8Vcx4XvAUdpkZfaz0KWfRrnIRrOndL0SvzEte+MTM7nDH4A2Now4FvTZ01yFAgj6TX/mZl8hQ==", "cpu": [ "loong64" ], @@ -1177,10 +1222,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.42.0.tgz", - "integrity": "sha512-6Qb66tbKVN7VyQrekhEzbHRxXXFFD8QKiFAwX5v9Xt6FiJ3BnCVBuyBxa2fkFGqxOCSGGYNejxd8ht+q5SnmtA==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.3.tgz", + "integrity": "sha512-VrQZp9tkk0yozJoQvQcqlWiqaPnLM6uY1qPYXvukKePb0fqaiQtOdMJSxNFUZFsGw5oA5vvVokjHrx8a9Qsz2A==", "cpu": [ "ppc64" ], @@ -1192,9 +1237,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.42.0.tgz", - "integrity": "sha512-KQETDSEBamQFvg/d8jajtRwLNBlGc3aKpaGiP/LvEbnmVUKlFta1vqJqTrvPtsYsfbE/DLg5CC9zyXRX3fnBiA==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.3.tgz", + "integrity": "sha512-uf2eucWSUb+M7b0poZ/08LsbcRgaDYL8NCGjUeFMwCWFwOuFcZ8D9ayPl25P3pl+D2FH45EbHdfyUesQ2Lt9wA==", "cpu": [ "riscv64" ], @@ -1206,9 +1251,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.42.0.tgz", - "integrity": "sha512-qMvnyjcU37sCo/tuC+JqeDKSuukGAd+pVlRl/oyDbkvPJ3awk6G6ua7tyum02O3lI+fio+eM5wsVd66X0jQtxw==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.3.tgz", + "integrity": "sha512-7tnUcDvN8DHm/9ra+/nF7lLzYHDeODKKKrh6JmZejbh1FnCNZS8zMkZY5J4sEipy2OW1d1Ncc4gNHUd0DLqkSg==", "cpu": [ "riscv64" ], @@ -1220,9 +1265,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.42.0.tgz", - "integrity": "sha512-I2Y1ZUgTgU2RLddUHXTIgyrdOwljjkmcZ/VilvaEumtS3Fkuhbw4p4hgHc39Ypwvo2o7sBFNl2MquNvGCa55Iw==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.3.tgz", + "integrity": "sha512-MUpAOallJim8CsJK+4Lc9tQzlfPbHxWDrGXZm2z6biaadNpvh3a5ewcdat478W+tXDoUiHwErX/dOql7ETcLqg==", "cpu": [ "s390x" ], @@ -1234,9 +1279,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.42.0.tgz", - "integrity": "sha512-Gfm6cV6mj3hCUY8TqWa63DB8Mx3NADoFwiJrMpoZ1uESbK8FQV3LXkhfry+8bOniq9pqY1OdsjFWNsSbfjPugw==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.3.tgz", + "integrity": "sha512-F42IgZI4JicE2vM2PWCe0N5mR5vR0gIdORPqhGQ32/u1S1v3kLtbZ0C/mi9FFk7C5T0PgdeyWEPajPjaUpyoKg==", "cpu": [ "x64" ], @@ -1248,9 +1293,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.42.0.tgz", - "integrity": "sha512-g86PF8YZ9GRqkdi0VoGlcDUb4rYtQKyTD1IVtxxN4Hpe7YqLBShA7oHMKU6oKTCi3uxwW4VkIGnOaH/El8de3w==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.3.tgz", + "integrity": "sha512-oLc+JrwwvbimJUInzx56Q3ujL3Kkhxehg7O1gWAYzm8hImCd5ld1F2Gry5YDjR21MNb5WCKhC9hXgU7rRlyegQ==", "cpu": [ "x64" ], @@ -1262,9 +1307,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.42.0.tgz", - "integrity": "sha512-+axkdyDGSp6hjyzQ5m1pgcvQScfHnMCcsXkx8pTgy/6qBmWVhtRVlgxjWwDp67wEXXUr0x+vD6tp5W4x6V7u1A==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.3.tgz", + "integrity": "sha512-lOrQ+BVRstruD1fkWg9yjmumhowR0oLAAzavB7yFSaGltY8klttmZtCLvOXCmGE9mLIn8IBV/IFrQOWz5xbFPg==", "cpu": [ "arm64" ], @@ -1276,9 +1321,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.42.0.tgz", - "integrity": "sha512-F+5J9pelstXKwRSDq92J0TEBXn2nfUrQGg+HK1+Tk7VOL09e0gBqUHugZv7SW4MGrYj41oNCUe3IKCDGVlis2g==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.3.tgz", + "integrity": "sha512-vvrVKPRS4GduGR7VMH8EylCBqsDcw6U+/0nPDuIjXQRbHJc6xOBj+frx8ksfZAh6+Fptw5wHrN7etlMmQnPQVg==", "cpu": [ "ia32" ], @@ -1290,9 +1335,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.42.0.tgz", - "integrity": "sha512-LpHiJRwkaVz/LqjHjK8LCi8osq7elmpwujwbXKNW88bM8eeGxavJIKKjkjpMHAh/2xfnrt1ZSnhTv41WYUHYmA==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.3.tgz", + "integrity": "sha512-fi3cPxCnu3ZeM3EwKZPgXbWoGzm2XHgB/WShKI81uj8wG0+laobmqy5wbgEwzstlbLu4MyO8C19FyhhWseYKNQ==", "cpu": [ "x64" ], @@ -1304,66 +1349,66 @@ ] }, "node_modules/@textlint/ast-node-types": { - "version": "14.8.0", - "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-14.8.0.tgz", - "integrity": "sha512-CARGqRSX+DhHdSYssa6+Yb0KAk5cGPDOgKbJo/H8djJAmw7qNzo/oYbuYZlO/fqmUbZjZcvI/6QgCxa/78Nxew==", + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-15.2.1.tgz", + "integrity": "sha512-20fEcLPsXg81yWpApv4FQxrZmlFF/Ta7/kz1HGIL+pJo5cSTmkc+eCki3GpOPZIoZk0tbJU8hrlwUb91F+3SNQ==", "dev": true, "license": "MIT" }, "node_modules/@textlint/ast-tester": { - "version": "14.8.0", - "resolved": "https://registry.npmjs.org/@textlint/ast-tester/-/ast-tester-14.8.0.tgz", - "integrity": "sha512-nRsgHmY+O7OhCYwGyWze+8mhnTYfCPFYTvuF3mCE5nQrfO9y2anvdjj2Yf6FU7OZI7qxp/R7MWYkiIQb/WEfHQ==", + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@textlint/ast-tester/-/ast-tester-15.2.1.tgz", + "integrity": "sha512-B3BNE52w+6eCsybpKhxIm/bMY1i3oF8AC5amYeaPaTaluz+rPDR4S5S6QAMaMM8XJlD0osYBdKd9LDwQPJFsIQ==", "dev": true, "license": "MIT", "dependencies": { - "@textlint/ast-node-types": "^14.8.0", + "@textlint/ast-node-types": "15.2.1", "debug": "^4.4.1" } }, "node_modules/@textlint/ast-traverse": { - "version": "14.8.0", - "resolved": "https://registry.npmjs.org/@textlint/ast-traverse/-/ast-traverse-14.8.0.tgz", - "integrity": "sha512-/u1SiIVnRFm1D/pglLtaP0QJND7UAo8axrUfaikFJZ67ciiguu17/yB0VBMbx9iZn5bmeUHH1NicgCnuirvvJg==", + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@textlint/ast-traverse/-/ast-traverse-15.2.1.tgz", + "integrity": "sha512-6i+kqjREEJ1HH3v0tltQ1ZTgptd4ViyJiZe+5J62Bn1Ml7CyV/zIJ4+3pJ4x26Ts+1sqpUD/lDDNOeZz5DKsmg==", "dev": true, "license": "MIT", "dependencies": { - "@textlint/ast-node-types": "^14.8.0" + "@textlint/ast-node-types": "15.2.1" } }, "node_modules/@textlint/config-loader": { - "version": "14.8.0", - "resolved": "https://registry.npmjs.org/@textlint/config-loader/-/config-loader-14.8.0.tgz", - "integrity": "sha512-WYg2WhyFCcCmEN1HOOpe420CMg9o7HzbELVGWvNrgNqqmQDxusUX88z1IG2xJ1bIGpgZTbm9SneUBnoRTzPCJg==", + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@textlint/config-loader/-/config-loader-15.2.1.tgz", + "integrity": "sha512-Rxp9YUAyYDmy6VIFJ0aNE/18O920wGCJpAVok+TnW8VM/CJXTp6/UibgSkqjWM5W2K301AFhZgPENAWJQLP7yw==", "dev": true, "license": "MIT", "dependencies": { - "@textlint/kernel": "^14.8.0", - "@textlint/module-interop": "^14.8.0", - "@textlint/resolver": "^14.8.0", - "@textlint/types": "^14.8.0", - "@textlint/utils": "^14.8.0", + "@textlint/kernel": "15.2.1", + "@textlint/module-interop": "15.2.1", + "@textlint/resolver": "15.2.1", + "@textlint/types": "15.2.1", + "@textlint/utils": "15.2.1", "debug": "^4.4.1", "rc-config-loader": "^4.1.3" } }, "node_modules/@textlint/feature-flag": { - "version": "14.8.0", - "resolved": "https://registry.npmjs.org/@textlint/feature-flag/-/feature-flag-14.8.0.tgz", - "integrity": "sha512-cmqPYs1EUYC/5YE8pd70ODrtHCeumR5kamK+CuNj2tS2lDPJ55XJt2I+UnX0SqyRATdl7Yp7OizLfZ5xrtAlUQ==", + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@textlint/feature-flag/-/feature-flag-15.2.1.tgz", + "integrity": "sha512-88H5WGxTperDNHA6LXT6AcTJ9MFs9l1OR7fjq+0AbXjq8Fg5RFYgx0SCxr4Fcmx0nr8JOFhexXp8qz6MMUz+bg==", "dev": true, "license": "MIT" }, "node_modules/@textlint/fixer-formatter": { - "version": "14.8.0", - "resolved": "https://registry.npmjs.org/@textlint/fixer-formatter/-/fixer-formatter-14.8.0.tgz", - "integrity": "sha512-eDpH/GQrod3Jg4HNkXw4SclSWLX85snUhzhMo1wmYgI8inRaIzfd1sURRy6edEabd6y1kLImyFmYOx9U96ILQA==", + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@textlint/fixer-formatter/-/fixer-formatter-15.2.1.tgz", + "integrity": "sha512-8TFs76YGJ+1rkIjQ5YXV4xhomYrkaMoqqST9d7UfGE1anj9koXNhSV4V8IHlK68dAQ91Qcc6Cdz/ohZ6Mv1t9Q==", "dev": true, "license": "MIT", "dependencies": { - "@textlint/module-interop": "^14.8.0", - "@textlint/resolver": "^14.8.0", - "@textlint/types": "^14.8.0", + "@textlint/module-interop": "15.2.1", + "@textlint/resolver": "15.2.1", + "@textlint/types": "15.2.1", "chalk": "^4.1.2", "debug": "^4.4.1", "diff": "^5.2.0", @@ -1418,36 +1463,36 @@ } }, "node_modules/@textlint/kernel": { - "version": "14.8.0", - "resolved": "https://registry.npmjs.org/@textlint/kernel/-/kernel-14.8.0.tgz", - "integrity": "sha512-AUy2qK7Z1pWBwtHjb3kdCwKjPo6M5SuS+e/Homvn77oUKXJVtJi4+HpDvVxTRZjW37LtYxIr/CyNhhNN8HAu4Q==", + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@textlint/kernel/-/kernel-15.2.1.tgz", + "integrity": "sha512-Xen6wfOlZZtGPsXqk9enXlXxsDy3uXBQo+fENXohT0e6xnx500r4N6IrYOr/VANMoAS2DrIrqpN6Q1W1VpBZlg==", "dev": true, "license": "MIT", "dependencies": { - "@textlint/ast-node-types": "^14.8.0", - "@textlint/ast-tester": "^14.8.0", - "@textlint/ast-traverse": "^14.8.0", - "@textlint/feature-flag": "^14.8.0", - "@textlint/source-code-fixer": "^14.8.0", - "@textlint/types": "^14.8.0", - "@textlint/utils": "^14.8.0", + "@textlint/ast-node-types": "15.2.1", + "@textlint/ast-tester": "15.2.1", + "@textlint/ast-traverse": "15.2.1", + "@textlint/feature-flag": "15.2.1", + "@textlint/source-code-fixer": "15.2.1", + "@textlint/types": "15.2.1", + "@textlint/utils": "15.2.1", "debug": "^4.4.1", "fast-equals": "^4.0.3", "structured-source": "^4.0.0" } }, "node_modules/@textlint/linter-formatter": { - "version": "14.8.0", - "resolved": "https://registry.npmjs.org/@textlint/linter-formatter/-/linter-formatter-14.8.0.tgz", - "integrity": "sha512-xedTBR/rlVNS9Np5moxhWGwVWEY8Eg+MxkasZblEZgiGbuMhCiVNrSuvx8T6E/UFvFunQYc9oNIMylzAXtnx7A==", + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@textlint/linter-formatter/-/linter-formatter-15.2.1.tgz", + "integrity": "sha512-oollG/BHa07+mMt372amxHohteASC+Zxgollc1sZgiyxo4S6EuureV3a4QIQB0NecA+Ak3d0cl0WI/8nou38jw==", "dev": true, "license": "MIT", "dependencies": { "@azu/format-text": "^1.0.2", "@azu/style-format": "^1.0.1", - "@textlint/module-interop": "^14.8.0", - "@textlint/resolver": "^14.8.0", - "@textlint/types": "^14.8.0", + "@textlint/module-interop": "15.2.1", + "@textlint/resolver": "15.2.1", + "@textlint/types": "15.2.1", "chalk": "^4.1.2", "debug": "^4.4.1", "js-yaml": "^3.14.1", @@ -1469,6 +1514,16 @@ "node": ">=8" } }, + "node_modules/@textlint/linter-formatter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, "node_modules/@textlint/linter-formatter/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -1476,6 +1531,20 @@ "dev": true, "license": "MIT" }, + "node_modules/@textlint/linter-formatter/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/@textlint/linter-formatter/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -1505,92 +1574,95 @@ } }, "node_modules/@textlint/markdown-to-ast": { - "version": "14.8.0", - "resolved": "https://registry.npmjs.org/@textlint/markdown-to-ast/-/markdown-to-ast-14.8.0.tgz", - "integrity": "sha512-KxvogGH8BPfR4eP5TNlRiR7KcPWzFjk1k8TX0WBnqWTzQeYbDYulYslVyiq06qc1NkTHvpK34zbS8UWZyzIQKA==", + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@textlint/markdown-to-ast/-/markdown-to-ast-15.2.1.tgz", + "integrity": "sha512-JU3AxIAuom/ZdKok6tkNVy8PsqjxKnSlBfld1dzyRV7/VFWbBuFdlaV6M8aJHa0HKy0Y9QHTXxiOJQkUPA5kWA==", "dev": true, "license": "MIT", "dependencies": { - "@textlint/ast-node-types": "^14.8.0", + "@textlint/ast-node-types": "15.2.1", "debug": "^4.4.1", "mdast-util-gfm-autolink-literal": "^0.1.3", - "neotraverse": "^0.6.15", + "neotraverse": "^0.6.18", "remark-footnotes": "^3.0.0", "remark-frontmatter": "^3.0.0", "remark-gfm": "^1.0.0", "remark-parse": "^9.0.0", + "structured-source": "^4.0.0", "unified": "^9.2.2" } }, "node_modules/@textlint/module-interop": { - "version": "14.8.0", - "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-14.8.0.tgz", - "integrity": "sha512-SGeojZIpjP58RrnUAIjKO5xokloHfXJWcc3dh/QP9pDHRCI97yPJhyEXzOD3FiY9zFG2KNIYwUTyrIGnnBm9xQ==", + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-15.2.1.tgz", + "integrity": "sha512-b/C/ZNrm05n1ypymDknIcpkBle30V2ZgE3JVqQlA9PnQV46Ky510qrZk6s9yfKgA3m1YRnAw04m8xdVtqjq1qg==", "dev": true, "license": "MIT" }, "node_modules/@textlint/resolver": { - "version": "14.8.0", - "resolved": "https://registry.npmjs.org/@textlint/resolver/-/resolver-14.8.0.tgz", - "integrity": "sha512-FUrXlwbfLxSUvgjOG/OgDV56m0IBBswcOEoex8cXAE1677ejAAWrI9WqzuBItX5g+srh5is3Vth4D6H8iuyLAg==", + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@textlint/resolver/-/resolver-15.2.1.tgz", + "integrity": "sha512-FY3aK4tElEcOJVUsaMj4Zro4jCtKEEwUMIkDL0tcn6ljNcgOF7Em+KskRRk/xowFWayqDtdz5T3u7w/6fjjuJQ==", "dev": true, "license": "MIT" }, "node_modules/@textlint/source-code-fixer": { - "version": "14.8.0", - "resolved": "https://registry.npmjs.org/@textlint/source-code-fixer/-/source-code-fixer-14.8.0.tgz", - "integrity": "sha512-/BtG3IRpmUG3A0Pr2wra2uY0d4sjmESeJlYn++ZlP8eYNYM9jotWsdb9K+fa1jMX4OzozKv1nOewhSfo/AD6Cw==", + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@textlint/source-code-fixer/-/source-code-fixer-15.2.1.tgz", + "integrity": "sha512-wMjEcH2jWfCazj2paNo5S+mnpf/ephOWceNQ5Aq1jfWCcqyJEvF8xykiCW7iFiW/KVkVIUdTdcrb+ZgY1n5bzw==", "dev": true, "license": "MIT", "dependencies": { - "@textlint/types": "^14.8.0", + "@textlint/types": "15.2.1", "debug": "^4.4.1" } }, "node_modules/@textlint/text-to-ast": { - "version": "14.8.0", - "resolved": "https://registry.npmjs.org/@textlint/text-to-ast/-/text-to-ast-14.8.0.tgz", - "integrity": "sha512-/U8k2Y6azqeKJnEJej2b8dylqKZw4tSqsOlfeI82qslDEjjdtseuzbLz7Z0X/VgWmbnqxrt1y/ZsLaThhOntuQ==", + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@textlint/text-to-ast/-/text-to-ast-15.2.1.tgz", + "integrity": "sha512-m6j+35PfBB1pWRQ7oddKhZWMPIqeEdGvQDyFvr50piyjiJ4ME7ASVhfvA3yoLQ+92jW/DWRQ+gpj0tQPdlmq8Q==", "dev": true, "license": "MIT", "dependencies": { - "@textlint/ast-node-types": "^14.8.0" + "@textlint/ast-node-types": "15.2.1" } }, "node_modules/@textlint/textlint-plugin-markdown": { - "version": "14.8.0", - "resolved": "https://registry.npmjs.org/@textlint/textlint-plugin-markdown/-/textlint-plugin-markdown-14.8.0.tgz", - "integrity": "sha512-BDjcoBfv+Vxg83/GrTg9XK4wKIuZb7x85gLmRqlsy48Lj5l++AIk5qe/iL1hI38PDr8IfY8JRYvfMpyn+KGuNA==", + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@textlint/textlint-plugin-markdown/-/textlint-plugin-markdown-15.2.1.tgz", + "integrity": "sha512-mHWjpGsVeIgVkWeW+H9sMhxKN/FNVofbqwwERg0vxme37vEVKrAXAq9t25ZL5erco5qkvuWT55LmgKOQG48oXw==", "dev": true, "license": "MIT", "dependencies": { - "@textlint/markdown-to-ast": "^14.8.0" + "@textlint/markdown-to-ast": "15.2.1", + "@textlint/types": "15.2.1" } }, "node_modules/@textlint/textlint-plugin-text": { - "version": "14.8.0", - "resolved": "https://registry.npmjs.org/@textlint/textlint-plugin-text/-/textlint-plugin-text-14.8.0.tgz", - "integrity": "sha512-fg2382TsRL7FiWxatvr3VNyjIQqMTRIqFkuPjBSb8HjC5xabi5s2ZU8Z0O58SBN9YWpihcTP+N84W5l5iU784g==", + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@textlint/textlint-plugin-text/-/textlint-plugin-text-15.2.1.tgz", + "integrity": "sha512-KI5vEEQXZJmEOrVuCeYVYHZLA6tWehwrvAYNjZLnlBICD8CTPLaFm3kqlDdGosbYnsk525BhEjW6sxAeA25a6g==", "dev": true, "license": "MIT", "dependencies": { - "@textlint/text-to-ast": "^14.8.0" + "@textlint/text-to-ast": "15.2.1", + "@textlint/types": "15.2.1" } }, "node_modules/@textlint/types": { - "version": "14.8.0", - "resolved": "https://registry.npmjs.org/@textlint/types/-/types-14.8.0.tgz", - "integrity": "sha512-Lsq2gxh2pmCKV6KN4fL70DMNNGuZlPuDQ0RHLU59/wgUs5krzrpHWCRYHK4M4J45U1PfZzQnWvLIfFETlR9GPA==", + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@textlint/types/-/types-15.2.1.tgz", + "integrity": "sha512-zyqNhSatK1cwxDUgosEEN43hFh3WCty9Zm2Vm3ogU566IYegifwqN54ey/CiRy/DiO4vMcFHykuQnh2Zwp6LLw==", "dev": true, "license": "MIT", "dependencies": { - "@textlint/ast-node-types": "^14.8.0" + "@textlint/ast-node-types": "15.2.1" } }, "node_modules/@textlint/utils": { - "version": "14.8.0", - "resolved": "https://registry.npmjs.org/@textlint/utils/-/utils-14.8.0.tgz", - "integrity": "sha512-ZYZuyPl7EW1Tio/jfjf92MFgPwrQ6nklir5uCJAwrdl9Me/9rL7l3n8HlFHc8Z7dPNeqUbDuOLgoS+74coZplA==", + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/@textlint/utils/-/utils-15.2.1.tgz", + "integrity": "sha512-osXG48HjqaOk21mX3upTmAMhEEIULjzZgH17S4pQzrU/16N0VsuiqYOZL14uN0gyWR8YY8lec+cszDN/eCwULg==", "dev": true, "license": "MIT" }, @@ -1618,6 +1690,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mdast": { "version": "3.0.15", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", @@ -1629,15 +1708,22 @@ } }, "node_modules/@types/node": { - "version": "22.15.30", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.30.tgz", - "integrity": "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==", + "version": "22.17.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.17.2.tgz", + "integrity": "sha512-gL6z5N9Jm9mhY+U2KXZpteb+09zyffliRkZyZOHODGATyC5B1Jt/7TzuuiLkFsSUMLbS1OLmlj/E+/3KF4Q/4w==", "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/unist": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", @@ -1663,9 +1749,9 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.3.tgz", - "integrity": "sha512-D1QKzngg8PcDoCE8FHSZhREDuEy+zcKmMiMafYse41RZpBE5EDJyKOTdqK3RQfsV2S2nyKor5KCs8PyPRFqKPg==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", + "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1687,8 +1773,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "3.2.3", - "vitest": "3.2.3" + "@vitest/browser": "3.2.4", + "vitest": "3.2.4" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -1697,15 +1783,15 @@ } }, "node_modules/@vitest/expect": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.3.tgz", - "integrity": "sha512-W2RH2TPWVHA1o7UmaFKISPvdicFJH+mjykctJFoAkUw+SPTJTGjUNdKscFBrqM7IPnCVu6zihtKYa7TkZS1dkQ==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", "dev": true, "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.3", - "@vitest/utils": "3.2.3", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" }, @@ -1714,13 +1800,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.3.tgz", - "integrity": "sha512-cP6fIun+Zx8he4rbWvi+Oya6goKQDZK+Yq4hhlggwQBbrlOQ4qtZ+G4nxB6ZnzI9lyIb+JnvyiJnPC2AGbKSPA==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.2.3", + "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, @@ -1741,9 +1827,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.3.tgz", - "integrity": "sha512-yFglXGkr9hW/yEXngO+IKMhP0jxyFw2/qys/CK4fFUZnSltD+MU7dVYGrH8rvPcK/O6feXQA+EU33gjaBBbAng==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", "dev": true, "license": "MIT", "dependencies": { @@ -1754,13 +1840,13 @@ } }, "node_modules/@vitest/runner": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.3.tgz", - "integrity": "sha512-83HWYisT3IpMaU9LN+VN+/nLHVBCSIUKJzGxC5RWUOsK1h3USg7ojL+UXQR3b4o4UBIWCYdD2fxuzM7PQQ1u8w==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.2.3", + "@vitest/utils": "3.2.4", "pathe": "^2.0.3", "strip-literal": "^3.0.0" }, @@ -1769,13 +1855,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.3.tgz", - "integrity": "sha512-9gIVWx2+tysDqUmmM1L0hwadyumqssOL1r8KJipwLx5JVYyxvVRfxvMq7DaWbZZsCqZnu/dZedaZQh4iYTtneA==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.3", + "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", "pathe": "^2.0.3" }, @@ -1784,9 +1870,9 @@ } }, "node_modules/@vitest/spy": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.3.tgz", - "integrity": "sha512-JHu9Wl+7bf6FEejTCREy+DmgWe+rQKbK+y32C/k5f4TBIAlijhJbRBIRIOCEpVevgRsCQR2iHRUH2/qKVM/plw==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", "dev": true, "license": "MIT", "dependencies": { @@ -1797,14 +1883,14 @@ } }, "node_modules/@vitest/utils": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.3.tgz", - "integrity": "sha512-4zFBCU5Pf+4Z6v+rwnZ1HU1yzOKKvDkMXZrymE2PBlbjKJRlrOxbvpfPSvJTGRIwGoahaOGvp+kbCoxifhzJ1Q==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.3", - "loupe": "^3.1.3", + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" }, "funding": { @@ -1843,9 +1929,9 @@ } }, "node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", + "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", "dev": true, "license": "MIT", "engines": { @@ -1872,14 +1958,11 @@ } }, "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } + "license": "Python-2.0" }, "node_modules/assertion-error": { "version": "2.0.1", @@ -1892,13 +1975,13 @@ } }, "node_modules/ast-v8-to-istanbul": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.3.tgz", - "integrity": "sha512-MuXMrSLVVoA6sYN/6Hke18vMzrT4TZNbZIj/hvh0fnYFpO+/kFXcLIaiPwXXWaQUPg4yJD8fj+lfJ7/1EBconw==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.4.tgz", + "integrity": "sha512-cxrAnZNLBnQwBPByK4CeDaw5sWZtMilJE/Q3iDA0aamgaIVNDF9T6K2/8DfYDZEejZ2jNnDrG9m8MY72HFd0KA==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", + "@jridgewell/trace-mapping": "^0.3.29", "estree-walker": "^3.0.3", "js-tokens": "^9.0.1" } @@ -1928,27 +2011,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], "license": "MIT" }, "node_modules/before-after-hook": { @@ -1986,37 +2049,13 @@ "license": "BSD-2-Clause" }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], "license": "MIT", "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "balanced-match": "^1.0.0" } }, "node_modules/bytes": { @@ -2040,14 +2079,14 @@ } }, "node_modules/cacheable": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.10.0.tgz", - "integrity": "sha512-SSgQTAnhd7WlJXnGlIi4jJJOiHzgnM5wRMEPaXAU4kECTAMpBoYKoZ9i5zHmclIEZbxcu3j7yY/CF8DTmwIsHg==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.10.4.tgz", + "integrity": "sha512-Gd7ccIUkZ9TE2odLQVS+PDjIvQCdJKUlLdJRVvZu0aipj07Qfx+XIej7hhDrKGGoIxV5m5fT/kOJNJPQhQneRg==", "dev": true, "license": "MIT", "dependencies": { - "hookified": "^1.8.2", - "keyv": "^5.3.3" + "hookified": "^1.11.0", + "keyv": "^5.5.0" } }, "node_modules/call-bind-apply-helpers": { @@ -2093,9 +2132,9 @@ } }, "node_modules/chai": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", - "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.1.tgz", + "integrity": "sha512-48af6xm9gQK8rhIcOxWwdGzIervm8BVTin+yRp9HEvU20BtVZ2lBywlIJBzwaDtvo0FvjeL7QdCADoUoqIbV3A==", "dev": true, "license": "MIT", "dependencies": { @@ -2106,7 +2145,7 @@ "pathval": "^2.0.0" }, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/chalk": { @@ -2405,16 +2444,6 @@ "node": ">= 0.8" } }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2456,9 +2485,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", - "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2469,31 +2498,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.5", - "@esbuild/android-arm": "0.25.5", - "@esbuild/android-arm64": "0.25.5", - "@esbuild/android-x64": "0.25.5", - "@esbuild/darwin-arm64": "0.25.5", - "@esbuild/darwin-x64": "0.25.5", - "@esbuild/freebsd-arm64": "0.25.5", - "@esbuild/freebsd-x64": "0.25.5", - "@esbuild/linux-arm": "0.25.5", - "@esbuild/linux-arm64": "0.25.5", - "@esbuild/linux-ia32": "0.25.5", - "@esbuild/linux-loong64": "0.25.5", - "@esbuild/linux-mips64el": "0.25.5", - "@esbuild/linux-ppc64": "0.25.5", - "@esbuild/linux-riscv64": "0.25.5", - "@esbuild/linux-s390x": "0.25.5", - "@esbuild/linux-x64": "0.25.5", - "@esbuild/netbsd-arm64": "0.25.5", - "@esbuild/netbsd-x64": "0.25.5", - "@esbuild/openbsd-arm64": "0.25.5", - "@esbuild/openbsd-x64": "0.25.5", - "@esbuild/sunos-x64": "0.25.5", - "@esbuild/win32-arm64": "0.25.5", - "@esbuild/win32-ia32": "0.25.5", - "@esbuild/win32-x64": "0.25.5" + "@esbuild/aix-ppc64": "0.25.9", + "@esbuild/android-arm": "0.25.9", + "@esbuild/android-arm64": "0.25.9", + "@esbuild/android-x64": "0.25.9", + "@esbuild/darwin-arm64": "0.25.9", + "@esbuild/darwin-x64": "0.25.9", + "@esbuild/freebsd-arm64": "0.25.9", + "@esbuild/freebsd-x64": "0.25.9", + "@esbuild/linux-arm": "0.25.9", + "@esbuild/linux-arm64": "0.25.9", + "@esbuild/linux-ia32": "0.25.9", + "@esbuild/linux-loong64": "0.25.9", + "@esbuild/linux-mips64el": "0.25.9", + "@esbuild/linux-ppc64": "0.25.9", + "@esbuild/linux-riscv64": "0.25.9", + "@esbuild/linux-s390x": "0.25.9", + "@esbuild/linux-x64": "0.25.9", + "@esbuild/netbsd-arm64": "0.25.9", + "@esbuild/netbsd-x64": "0.25.9", + "@esbuild/openbsd-arm64": "0.25.9", + "@esbuild/openbsd-x64": "0.25.9", + "@esbuild/openharmony-arm64": "0.25.9", + "@esbuild/sunos-x64": "0.25.9", + "@esbuild/win32-arm64": "0.25.9", + "@esbuild/win32-ia32": "0.25.9", + "@esbuild/win32-x64": "0.25.9" } }, "node_modules/escape-html": { @@ -2564,19 +2594,19 @@ } }, "node_modules/eventsource-parser": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.2.tgz", - "integrity": "sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.5.tgz", + "integrity": "sha512-bSRG85ZrMdmWtm7qkF9He9TNRzc/Bm99gEJMaQoHJ9E6Kv9QBbsldh2oMj7iXmYNEAVvNgvv5vPorG6W+XtBhQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/expect-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", - "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", + "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2627,9 +2657,9 @@ } }, "node_modules/express-rate-limit": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", - "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", + "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", "dev": true, "license": "MIT", "engines": { @@ -2639,7 +2669,7 @@ "url": "https://github.com/sponsors/express-rate-limit" }, "peerDependencies": { - "express": "^4.11 || 5 || ^5.0.0-beta.1" + "express": ">= 4.11" } }, "node_modules/extend": { @@ -2725,11 +2755,14 @@ } }, "node_modules/fdir": { - "version": "6.4.5", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", - "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -2740,13 +2773,13 @@ } }, "node_modules/file-entry-cache": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.1.1.tgz", - "integrity": "sha512-zcmsHjg2B2zjuBgjdnB+9q0+cWcgWfykIcsDkWDB4GTPtl1eXUA+gTI6sO0u01AqK3cliHryTU55/b2Ow1hfZg==", + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.1.4.tgz", + "integrity": "sha512-5XRUFc0WTtUbjfGzEwXc42tiGxQHBmtbUG1h9L2apu4SulCGN3Hqm//9D6FAolf8MYNL7f/YlJl9vy08pj5JuA==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^6.1.10" + "flat-cache": "^6.1.13" } }, "node_modules/finalhandler": { @@ -2767,29 +2800,29 @@ "node": ">= 0.8" } }, - "node_modules/find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "node_modules/find-up-simple": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", "dev": true, "license": "MIT", - "dependencies": { - "locate-path": "^2.0.0" - }, "engines": { - "node": ">=4" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/flat-cache": { - "version": "6.1.10", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.10.tgz", - "integrity": "sha512-B6/v1f0NwjxzmeOhzfXPGWpKBVA207LS7lehaVKQnFrVktcFRfkzjZZ2gwj2i1TkEUMQht7ZMJbABUT5N+V1Nw==", + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.13.tgz", + "integrity": "sha512-gmtS2PaUjSPa4zjObEIn4WWliKyZzYljgxODBfxugpK6q6HU9ClXzgCJ+nlcPKY9Bt090ypTOLIFWkV0jbKFjw==", "dev": true, "license": "MIT", "dependencies": { - "cacheable": "^1.10.0", + "cacheable": "^1.10.4", "flatted": "^3.3.3", - "hookified": "^1.9.1" + "hookified": "^1.11.0" } }, "node_modules/flatted": { @@ -2972,13 +3005,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3016,18 +3042,24 @@ } }, "node_modules/hookified": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.9.1.tgz", - "integrity": "sha512-u3pxtGhKjcSXnGm1CX6aXS9xew535j3lkOCegbA6jdyh0BaAjTbXI4aslKstCr6zUNtoCxFGFKwjbSHdGrMB8g==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.11.0.tgz", + "integrity": "sha512-aDdIN3GyU5I6wextPplYdfmWCo+aLmjjVbntmX6HLD5RCi/xKsivYEBhnRD+d9224zFf008ZpLMPlWF0ZodYZw==", "dev": true, "license": "MIT" }, "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", "dev": true, - "license": "ISC" + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } }, "node_modules/html-escaper": { "version": "2.0.2", @@ -3076,26 +3108,18 @@ "node": ">=0.10.0" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "node_modules/index-to-position": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.1.0.tgz", + "integrity": "sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/inherits": { "version": "2.0.4", @@ -3140,13 +3164,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, "node_modules/is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", @@ -3154,22 +3171,6 @@ "dev": true, "license": "MIT" }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-decimal": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", @@ -3219,13 +3220,6 @@ "dev": true, "license": "MIT" }, - "node_modules/is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", - "dev": true, - "license": "MIT" - }, "node_modules/isexe": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", @@ -3276,9 +3270,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -3313,26 +3307,18 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "license": "MIT", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true, - "license": "MIT" - }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -3354,13 +3340,13 @@ } }, "node_modules/keyv": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.3.3.tgz", - "integrity": "sha512-Rwu4+nXI9fqcxiEHtbkvoes2X+QfkTRo1TMkPfwzipGsJlJO/z69vqB4FNl9xJ3xCpAcbkvmEabZfPzrwN3+gQ==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.0.tgz", + "integrity": "sha512-QG7qR2tijh1ftOvClut4YKKg1iW6cx3GZsKoGyJPxHkGWK9oJhG9P3j5deP0QQOGDowBMVQFaP+Vm4NpGYvmIQ==", "dev": true, "license": "MIT", "dependencies": { - "@keyv/serialize": "^1.0.3" + "@keyv/serialize": "^1.1.0" } }, "node_modules/levn": { @@ -3377,37 +3363,6 @@ "node": ">= 0.8.0" } }, - "node_modules/load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -3434,9 +3389,9 @@ } }, "node_modules/loupe": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", - "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.0.tgz", + "integrity": "sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==", "dev": true, "license": "MIT" }, @@ -3890,12 +3845,12 @@ } }, "node_modules/minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { "node": "20 || >=22" @@ -3994,26 +3949,18 @@ } }, "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/object-assign": { @@ -4063,9 +4010,9 @@ } }, "node_modules/openai": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-5.3.0.tgz", - "integrity": "sha512-VIKmoF7y4oJCDOwP/oHXGzM69+x0dpGFmN9QmYO+uPbLFOmmnwO+x1GbsgUtI+6oraxomGZ566Y421oYVu191w==", + "version": "5.12.2", + "resolved": "https://registry.npmjs.org/openai/-/openai-5.12.2.tgz", + "integrity": "sha512-xqzHHQch5Tws5PcKR2xsZGX9xtch+JQFz5zb14dGqlshmmDAFBFEWmeIpf7wVqWV+w7Emj7jRgkNJakyKE0tYQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4103,56 +4050,20 @@ } }, "node_modules/p-limit": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz", - "integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.0.0.tgz", + "integrity": "sha512-WeCdPG5OjujcMWjSkOS0kt3bo+LmroXLmOnJ4SPhZfz5pffQxDUNcYscbZgyGwKf9r9z7gRfKjDNno5cZyQAZQ==", "license": "MIT", "dependencies": { - "yocto-queue": "^1.1.1" + "yocto-queue": "^1.2.1" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -4180,16 +4091,21 @@ } }, "node_modules/parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", "dev": true, "license": "MIT", "dependencies": { - "error-ex": "^1.2.0" + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/parseurl": { @@ -4202,16 +4118,6 @@ "node": ">= 0.8" } }, - "node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -4222,13 +4128,6 @@ "node": ">=8" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", @@ -4263,21 +4162,6 @@ "node": ">=16" } }, - "node_modules/path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -4286,9 +4170,9 @@ "license": "MIT" }, "node_modules/pathval": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", - "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", "dev": true, "license": "MIT", "engines": { @@ -4303,9 +4187,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==", "dev": true, "license": "MIT", "engines": { @@ -4315,39 +4199,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "pinkie": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/pkce-challenge": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", @@ -4366,9 +4217,9 @@ "license": "MIT" }, "node_modules/postcss": { - "version": "8.5.4", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz", - "integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -4483,131 +4334,42 @@ "require-from-string": "^2.0.2" } }, - "node_modules/rc-config-loader/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/rc-config-loader/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up/node_modules/load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "node_modules/read-package-up": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", + "integrity": "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==", "dev": true, "license": "MIT", "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" + "find-up-simple": "^1.0.0", + "read-pkg": "^9.0.0", + "type-fest": "^4.6.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up/node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "dev": true, - "license": "MIT", - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "node": ">=18" }, - "engines": { - "node": ">=4" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/read-pkg-up/node_modules/path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "node_modules/read-pkg": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", "dev": true, "license": "MIT", "dependencies": { - "pify": "^3.0.0" + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up/node_modules/read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" + "node": ">=18" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/remark-footnotes": { @@ -4689,27 +4451,6 @@ "node": ">=0.10.0" } }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -4721,13 +4462,13 @@ } }, "node_modules/rollup": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.42.0.tgz", - "integrity": "sha512-LW+Vse3BJPyGJGAJt1j8pWDKPd73QM8cRXYK1IxOBgL2AGLu7Xd2YOW0M2sLUBCkF5MshXXtMApyEAEzMVMsnw==", + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.3.tgz", + "integrity": "sha512-RZn2XTjXb8t5g13f5YclGoilU/kwT696DIkY3sywjdZidNSi3+vseaQov7D7BZXVJCPv3pDWUN69C78GGbXsKw==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.7" + "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" @@ -4737,36 +4478,29 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.42.0", - "@rollup/rollup-android-arm64": "4.42.0", - "@rollup/rollup-darwin-arm64": "4.42.0", - "@rollup/rollup-darwin-x64": "4.42.0", - "@rollup/rollup-freebsd-arm64": "4.42.0", - "@rollup/rollup-freebsd-x64": "4.42.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.42.0", - "@rollup/rollup-linux-arm-musleabihf": "4.42.0", - "@rollup/rollup-linux-arm64-gnu": "4.42.0", - "@rollup/rollup-linux-arm64-musl": "4.42.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.42.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.42.0", - "@rollup/rollup-linux-riscv64-gnu": "4.42.0", - "@rollup/rollup-linux-riscv64-musl": "4.42.0", - "@rollup/rollup-linux-s390x-gnu": "4.42.0", - "@rollup/rollup-linux-x64-gnu": "4.42.0", - "@rollup/rollup-linux-x64-musl": "4.42.0", - "@rollup/rollup-win32-arm64-msvc": "4.42.0", - "@rollup/rollup-win32-ia32-msvc": "4.42.0", - "@rollup/rollup-win32-x64-msvc": "4.42.0", + "@rollup/rollup-android-arm-eabi": "4.46.3", + "@rollup/rollup-android-arm64": "4.46.3", + "@rollup/rollup-darwin-arm64": "4.46.3", + "@rollup/rollup-darwin-x64": "4.46.3", + "@rollup/rollup-freebsd-arm64": "4.46.3", + "@rollup/rollup-freebsd-x64": "4.46.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.46.3", + "@rollup/rollup-linux-arm-musleabihf": "4.46.3", + "@rollup/rollup-linux-arm64-gnu": "4.46.3", + "@rollup/rollup-linux-arm64-musl": "4.46.3", + "@rollup/rollup-linux-loongarch64-gnu": "4.46.3", + "@rollup/rollup-linux-ppc64-gnu": "4.46.3", + "@rollup/rollup-linux-riscv64-gnu": "4.46.3", + "@rollup/rollup-linux-riscv64-musl": "4.46.3", + "@rollup/rollup-linux-s390x-gnu": "4.46.3", + "@rollup/rollup-linux-x64-gnu": "4.46.3", + "@rollup/rollup-linux-x64-musl": "4.46.3", + "@rollup/rollup-win32-arm64-msvc": "4.46.3", + "@rollup/rollup-win32-ia32-msvc": "4.46.3", + "@rollup/rollup-win32-x64-msvc": "4.46.3", "fsevents": "~2.3.2" } }, - "node_modules/rollup/node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", - "dev": true, - "license": "MIT" - }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -5048,9 +4782,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.21", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", - "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", "dev": true, "license": "CC0-1.0" }, @@ -5189,23 +4923,10 @@ "node": ">=8" } }, - "node_modules/strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-utf8": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/strip-json-comments": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.2.tgz", - "integrity": "sha512-4X2FR3UwhNUE9G49aIsJW5hRRR3GXGTBTZRMfv568O60ojM8HcWjV/VxAxCDW3SUND33O6ZY66ZuRcdkj73q2g==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", + "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", "dev": true, "license": "MIT", "engines": { @@ -5251,19 +4972,6 @@ "node": ">=8" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/table": { "version": "6.9.0", "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", @@ -5389,45 +5097,44 @@ "license": "MIT" }, "node_modules/textlint": { - "version": "14.8.0", - "resolved": "https://registry.npmjs.org/textlint/-/textlint-14.8.0.tgz", - "integrity": "sha512-1+Y78J7b509CagmxxhceRRF99KXNuUBjstMIGc2pp/CkjY/vdr6s1AObWMiiWF7asm3UFmgfqMxl+tNIlvuT5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@modelcontextprotocol/sdk": "^1.12.1", - "@textlint/ast-node-types": "^14.8.0", - "@textlint/ast-traverse": "^14.8.0", - "@textlint/config-loader": "^14.8.0", - "@textlint/feature-flag": "^14.8.0", - "@textlint/fixer-formatter": "^14.8.0", - "@textlint/kernel": "^14.8.0", - "@textlint/linter-formatter": "^14.8.0", - "@textlint/module-interop": "^14.8.0", - "@textlint/resolver": "^14.8.0", - "@textlint/textlint-plugin-markdown": "^14.8.0", - "@textlint/textlint-plugin-text": "^14.8.0", - "@textlint/types": "^14.8.0", - "@textlint/utils": "^14.8.0", + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/textlint/-/textlint-15.2.1.tgz", + "integrity": "sha512-FChEKsi81lcjv9ZucXpbMNA3HQw27eafUw0F7fyiX56u8eNr2rSb5oAtZO0DF6a+/bNDe8W7NG3BxOAPsWFoDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.15.1", + "@textlint/ast-node-types": "15.2.1", + "@textlint/ast-traverse": "15.2.1", + "@textlint/config-loader": "15.2.1", + "@textlint/feature-flag": "15.2.1", + "@textlint/fixer-formatter": "15.2.1", + "@textlint/kernel": "15.2.1", + "@textlint/linter-formatter": "15.2.1", + "@textlint/module-interop": "15.2.1", + "@textlint/resolver": "15.2.1", + "@textlint/textlint-plugin-markdown": "15.2.1", + "@textlint/textlint-plugin-text": "15.2.1", + "@textlint/types": "15.2.1", + "@textlint/utils": "15.2.1", "debug": "^4.4.1", - "file-entry-cache": "^10.0.5", + "file-entry-cache": "^10.1.1", "glob": "^10.4.5", "md5": "^2.3.0", "mkdirp": "^0.5.6", - "optionator": "^0.9.3", + "optionator": "^0.9.4", "path-to-glob-pattern": "^2.0.1", "rc-config-loader": "^4.1.3", - "read-pkg": "^1.1.0", - "read-pkg-up": "^3.0.0", + "read-package-up": "^11.0.0", "structured-source": "^4.0.0", "unique-concat": "^0.2.2", - "zod": "^3.25.56" + "zod": "^3.25.76" }, "bin": { "textlint": "bin/textlint.js" }, "engines": { - "node": ">=18.14.0" + "node": ">=20.0.0" } }, "node_modules/textlint-filter-rule-comments": { @@ -5441,28 +5148,21 @@ } }, "node_modules/textlint-rule-helper": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/textlint-rule-helper/-/textlint-rule-helper-2.3.1.tgz", - "integrity": "sha512-b1bijvyiUmKinfFE5hkQMSXs3Ky8jyZ3Y6SOoTRJKV9HLL2LWUVFAUezO7z4FpAkVvYruDYWCwA5qWV8GmvyUw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/textlint-rule-helper/-/textlint-rule-helper-2.5.0.tgz", + "integrity": "sha512-QIbFPtyqLy0g5BJn8mryk9iHzGYicNaFIpLFPiEnb4RXxrEGeQ2W2aARQ9yEXLIAqo+OwK4ndWBAWkbgJEPzTQ==", "dev": true, "license": "MIT", "dependencies": { - "@textlint/ast-node-types": "^13.4.1", + "@textlint/ast-node-types": "^15.2.1", "structured-source": "^4.0.0", "unist-util-visit": "^2.0.3" } }, - "node_modules/textlint-rule-helper/node_modules/@textlint/ast-node-types": { - "version": "13.4.1", - "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-13.4.1.tgz", - "integrity": "sha512-qrZyhCh8Ekk6nwArx3BROybm9BnX6vF7VcZbijetV/OM3yfS4rTYhoMWISmhVEP2H2re0CtWEyMl/XF+WdvVLQ==", - "dev": true, - "license": "MIT" - }, "node_modules/textlint-rule-terminology": { - "version": "5.2.12", - "resolved": "https://registry.npmjs.org/textlint-rule-terminology/-/textlint-rule-terminology-5.2.12.tgz", - "integrity": "sha512-qLyuqbGN7GBqKR7NT2yLyo8mmYmoCkk8bu3szvLxUkM+1Cc0D9G6MDyaYFrMM26GZuNbTREmuFxon/vTBDR/pw==", + "version": "5.2.14", + "resolved": "https://registry.npmjs.org/textlint-rule-terminology/-/textlint-rule-terminology-5.2.14.tgz", + "integrity": "sha512-OYvLq+K+HpQqxWhV9MXb/jnND8oWxUbZK+OmrqO8auO2TWMky22ZlR8qoAmlzYbRMR1S3LJyifFlThZoXbD1uA==", "dev": true, "license": "MIT", "dependencies": { @@ -5506,9 +5206,9 @@ } }, "node_modules/tinypool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.0.tgz", - "integrity": "sha512-7CotroY9a8DKsKprEy/a14aCCm8jYVmR7aFy4fpkZM8sdpNJbKkixuNjgM50yCmip2ezc8z4N7k3oe2+rfRJCQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", "dev": true, "license": "MIT", "engines": { @@ -5567,9 +5267,9 @@ } }, "node_modules/tsx": { - "version": "4.19.4", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.4.tgz", - "integrity": "sha512-gK5GVzDkJK1SI1zwHf32Mqxf2tSJkNx+eYcNly5+nHvWqXUJYUkWBQtKauoESz3ymezAI++ZwT855x5p5eop+Q==", + "version": "4.20.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.4.tgz", + "integrity": "sha512-yyxBKfORQ7LuRt/BQKBXrpcq59ZvSW0XxwfjAt3w2/8PmdxaFzijtMhTawprSHhpzeM5BgU2hXHG3lklIERZXg==", "dev": true, "license": "MIT", "dependencies": { @@ -5608,6 +5308,19 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/type-is": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", @@ -5624,9 +5337,9 @@ } }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, "license": "Apache-2.0", "bin": { @@ -5656,6 +5369,19 @@ "dev": true, "license": "MIT" }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unified": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.2.tgz", @@ -5866,24 +5592,24 @@ } }, "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz", + "integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" + "fdir": "^6.4.6", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.14" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -5892,14 +5618,14 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", - "less": "*", + "less": "^4.0.0", "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", + "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" @@ -5941,9 +5667,9 @@ } }, "node_modules/vite-node": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.3.tgz", - "integrity": "sha512-gc8aAifGuDIpZHrPjuHyP4dpQmYXqWw7D1GmDnWeNWP654UEXzVfQ5IHPSK5HaHkwB/+p1atpYpSdw/2kOv8iQ==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", "dev": true, "license": "MIT", "dependencies": { @@ -5964,20 +5690,20 @@ } }, "node_modules/vitest": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.3.tgz", - "integrity": "sha512-E6U2ZFXe3N/t4f5BwUaVCKRLHqUpk1CBWeMh78UT4VaTPH/2dyvH6ALl29JTovEPu9dVKr/K/J4PkXgrMbw4Ww==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", - "@vitest/expect": "3.2.3", - "@vitest/mocker": "3.2.3", - "@vitest/pretty-format": "^3.2.3", - "@vitest/runner": "3.2.3", - "@vitest/snapshot": "3.2.3", - "@vitest/spy": "3.2.3", - "@vitest/utils": "3.2.3", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", @@ -5988,10 +5714,10 @@ "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", - "tinypool": "^1.1.0", + "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", - "vite-node": "3.2.3", + "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "bin": { @@ -6007,8 +5733,8 @@ "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.2.3", - "@vitest/ui": "3.2.3", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, @@ -6193,9 +5919,9 @@ } }, "node_modules/zod": { - "version": "3.25.57", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.57.tgz", - "integrity": "sha512-6tgzLuwVST5oLUxXTmBqoinKMd3JeesgbgseXeFasKKj8Q1FCZrHnbqJOyiEvr4cVAlbug+CgIsmJ8cl/pU5FA==", + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "dev": true, "license": "MIT", "funding": { @@ -6203,9 +5929,9 @@ } }, "node_modules/zod-to-json-schema": { - "version": "3.24.5", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", - "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", + "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", "dev": true, "license": "ISC", "peerDependencies": { diff --git a/package.json b/package.json index 3ff85fb4..c1a52f13 100644 --- a/package.json +++ b/package.json @@ -54,20 +54,22 @@ "@octokit/plugin-rest-endpoint-methods": "^16.0.0", "@octokit/request-error": "^7.0.0", "minimatch": "^10.0.1", - "p-limit": "^6.2.0", + "p-limit": "^7.0.0", "which": "^5.0.0" }, "devDependencies": { - "@biomejs/biome": "^1.9.4", + "@biomejs/biome": "^2.2.0", "@octokit/types": "^14.0.0", "@octokit/webhooks-types": "^7.6.1", - "@types/node": "^22.15.29", + "@types/js-yaml": "^4.0.9", + "@types/node": "^22.17.2", "@types/which": "^3.0.4", "@vercel/ncc": "^0.38.3", "@vitest/coverage-v8": "^3.1.3", + "js-yaml": "^4.1.0", "make-coverage-badge": "^1.2.0", "openai": "latest", - "textlint": "^14.8.0", + "textlint": "^15.2.1", "textlint-filter-rule-comments": "^1.2.2", "textlint-rule-terminology": "^5.2.12", "ts-deepmerge": "^7.0.2", diff --git a/scripts/dev-parse-modules.ts b/scripts/dev-parse-modules.ts index e395c7fa..f472f80e 100644 --- a/scripts/dev-parse-modules.ts +++ b/scripts/dev-parse-modules.ts @@ -37,11 +37,11 @@ async function main() { process.env['INPUT_MODULE-CHANGE-EXCLUDE-PATTERNS'] = '.gitignore,*.md'; // Initialize - const config = getConfig(); - const context = getContext(); + const _config = getConfig(); + const _context = getContext(); // Test with empty tags and releases for now - const modules = parseTerraformModules( + const _modules = parseTerraformModules( [ { message: 'feat: add screenshots for documentation', diff --git a/src/config.ts b/src/config.ts index 4a620f18..f5e21e8e 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,30 +1,11 @@ import type { Config } from '@/types'; -import { endGroup, getBooleanInput, getInput, info, startGroup } from '@actions/core'; +import { VALID_TAG_DIRECTORY_SEPARATORS, VERSION_TAG_REGEX } from '@/utils/constants'; +import { createConfigFromInputs } from '@/utils/metadata'; +import { endGroup, info, startGroup } from '@actions/core'; // Keep configInstance private to this module let configInstance: Config | null = null; -/** - * Retrieves an array of values from a comma-separated input string. Duplicates any empty values - * are removed and each value is trimmed of whitespace. - * - * @param inputName - Name of the input to retrieve. - * @param required - Whether the input is required. - * @returns An array of trimmed and filtered values. - */ -const getArrayInput = (inputName: string, required: boolean): string[] => { - const input = getInput(inputName, { required }); - - return Array.from( - new Set( - input - .split(',') - .map((item: string) => item.trim()) - .filter(Boolean), - ), - ); -}; - /** * Clears the cached config instance during testing. * @@ -58,24 +39,8 @@ function initializeConfig(): Config { try { startGroup('Initializing Config'); - // Initialize the config instance - configInstance = { - majorKeywords: getArrayInput('major-keywords', true), - minorKeywords: getArrayInput('minor-keywords', true), - patchKeywords: getArrayInput('patch-keywords', true), - defaultFirstTag: getInput('default-first-tag', { required: true }), - terraformDocsVersion: getInput('terraform-docs-version', { required: true }), - deleteLegacyTags: getBooleanInput('delete-legacy-tags', { required: true }), - disableWiki: getBooleanInput('disable-wiki', { required: true }), - wikiSidebarChangelogMax: Number.parseInt(getInput('wiki-sidebar-changelog-max', { required: true }), 10), - disableBranding: getBooleanInput('disable-branding', { required: true }), - githubToken: getInput('github_token', { required: true }), - modulePathIgnore: getArrayInput('module-path-ignore', false), - moduleChangeExcludePatterns: getArrayInput('module-change-exclude-patterns', false), - moduleAssetExcludePatterns: getArrayInput('module-asset-exclude-patterns', false), - useSSHSourceFormat: getBooleanInput('use-ssh-source-format', { required: true }), - wikiUsageTemplate: getInput('wiki-usage-template', { required: false }), - }; + // Initialize the config instance using action metadata + configInstance = createConfigFromInputs(); // Validate that *.tf is not in excludePatterns if (configInstance.moduleChangeExcludePatterns.some((pattern) => pattern === '*.tf')) { @@ -85,13 +50,35 @@ function initializeConfig(): Config { throw new TypeError('Asset exclude patterns cannot contain "*.tf" as these files are required'); } - // Validate that we have a valid first tag. For now, must be v#.#.# - // Validate WikiSidebar Changelog Max is a number and greater than zero if (configInstance.wikiSidebarChangelogMax < 1 || Number.isNaN(configInstance.wikiSidebarChangelogMax)) { throw new TypeError('Wiki Sidebar Change Log Max must be an integer greater than or equal to one'); } + // Validate tag directory separator + if (configInstance.tagDirectorySeparator.length !== 1) { + throw new TypeError('Tag directory separator must be exactly one character'); + } + if (!VALID_TAG_DIRECTORY_SEPARATORS.includes(configInstance.tagDirectorySeparator)) { + throw new TypeError( + `Tag directory separator must be one of: ${VALID_TAG_DIRECTORY_SEPARATORS.join(', ')}. Got: '${ + configInstance.tagDirectorySeparator + }'`, + ); + } + // Validate default first tag format + if (!VERSION_TAG_REGEX.test(configInstance.defaultFirstTag)) { + throw new TypeError( + `Default first tag must be in format v#.#.# or #.#.# (e.g., v1.0.0 or 1.0.0). Got: '${configInstance.defaultFirstTag}'`, + ); + } + + // If we aren't using "v" prefix but the default first tag was specified with a "v" + // prefix, then strip this to enforce. + if (!configInstance.useVersionPrefix && configInstance.defaultFirstTag.startsWith('v')) { + configInstance.defaultFirstTag = configInstance.defaultFirstTag.substring(1); + } + info(`Major Keywords: ${configInstance.majorKeywords.join(', ')}`); info(`Minor Keywords: ${configInstance.minorKeywords.join(', ')}`); info(`Patch Keywords: ${configInstance.patchKeywords.join(', ')}`); @@ -104,6 +91,8 @@ function initializeConfig(): Config { info(`Module Change Exclude Patterns: ${configInstance.moduleChangeExcludePatterns.join(', ')}`); info(`Module Asset Exclude Patterns: ${configInstance.moduleAssetExcludePatterns.join(', ')}`); info(`Use SSH Source Format: ${configInstance.useSSHSourceFormat}`); + info(`Tag Directory Separator: ${configInstance.tagDirectorySeparator}`); + info(`Use Version Prefix: ${configInstance.useVersionPrefix}`); return configInstance; } finally { @@ -118,7 +107,7 @@ export function getConfig(): Config { // For backward compatibility and existing usage export const config: Config = new Proxy({} as Config, { - get(target, prop) { + get(_target, prop) { return getConfig()[prop as keyof Config]; }, }); diff --git a/src/context.ts b/src/context.ts index a4d76a65..0da5df19 100644 --- a/src/context.ts +++ b/src/context.ts @@ -165,7 +165,7 @@ export const getContext = (): Context => { // For backward compatibility and existing usage export const context: Context = new Proxy({} as Context, { - get(target, prop) { + get(_target, prop) { return getContext()[prop as keyof Context]; }, }); diff --git a/src/templating.ts b/src/templating.ts deleted file mode 100644 index da3eec67..00000000 --- a/src/templating.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Renders a template string by replacing placeholders with provided values. - * - * @param template The template string containing placeholders in the format `{{key}}`. - * @param variables An object where keys correspond to placeholder names and values are their replacements. - * @returns The rendered string with placeholders replaced. - */ -export const render = (template: string, variables: Record): string => { - return template.replace(/\{\{(\w+)\}\}/g, (placeholder, key) => { - return key in variables ? variables[key] : placeholder; - }); -}; diff --git a/src/terraform-module.ts b/src/terraform-module.ts index 7da8f36f..c18d017b 100644 --- a/src/terraform-module.ts +++ b/src/terraform-module.ts @@ -2,8 +2,14 @@ import { relative } from 'node:path'; import { config } from '@/config'; import { context } from '@/context'; import type { CommitDetails, GitHubRelease, ReleaseReason, ReleaseType } from '@/types'; -import { MODULE_TAG_REGEX, RELEASE_REASON, RELEASE_TYPE, VERSION_TAG_REGEX } from '@/utils/constants'; -import { removeTrailingCharacters } from '@/utils/string'; +import { + MODULE_TAG_REGEX, + RELEASE_REASON, + RELEASE_TYPE, + VALID_TAG_DIRECTORY_SEPARATORS, + VERSION_TAG_REGEX, +} from '@/utils/constants'; +import { removeLeadingCharacters, removeTrailingCharacters } from '@/utils/string'; import { endGroup, info, startGroup } from '@actions/core'; /** @@ -213,11 +219,14 @@ export class TerraformModule { * @returns {string | null} The version string including any prefixes (e.g., 'v1.2.3' or '1.2.3'), or null if no tags exist. */ public getLatestTagVersion(): string | null { - if (this.tags.length === 0) { + const latestTag = this.getLatestTag(); + if (latestTag === null) { return null; } - return this.tags[0].replace(`${this.name}/`, ''); + const match = MODULE_TAG_REGEX.exec(latestTag); + + return match ? match[3] : null; } /** @@ -452,8 +461,7 @@ export class TerraformModule { semver[2]++; } - // Hard coding "v" for now. Potentially fixing in the future. - return `v${semver.join('.')}`; + return `${config.useVersionPrefix ? 'v' : ''}${semver.join('.')}`; } /** @@ -476,41 +484,49 @@ export class TerraformModule { return null; } - return `${this.name}/${releaseTagVersion}`; + return `${this.name}${config.tagDirectorySeparator}${releaseTagVersion}`; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Helper ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** - * Safely extracts the numerical version string from a tag, avoiding regex vulnerabilities. - * Handles tags in format: moduleName/vX.Y.Z or moduleName/X.Y.Z - * Also validates that tag format matches expected pattern and returns only the numerical part. + * Extracts and validates the version string from a Terraform module tag. * - * @param {string} tag - The tag string to extract version from - * @returns {string} The numerical version string (e.g., "1.2.3") - * @throws {Error} If the tag does not match the required format + * Uses the MODULE_TAG_REGEX to validate the tag format and extract version components. + * This method leverages the static validation logic to ensure consistent tag processing + * across different separator formats (/, -, _, .). + * + * @param {string} tag - The tag string to extract version from (e.g., "module/v1.2.3", "module-v1.2.3") + * @returns {string} The numerical version string without prefix (e.g., "1.2.3") + * @throws {Error} If the tag does not match the required format or is not associated with this module */ private extractVersionFromTag(tag: string): string { - // Validate tag format - must start with module name followed by slash - if (!tag.startsWith(`${this.name}/`)) { + // Use the static validation method to ensure the tag is associated with this module + if (!TerraformModule.isModuleAssociatedWithTag(this.name, tag)) { throw new Error( - `Invalid tag format: '${tag}'. Expected format: '${this.name}/v#.#.#' or '${this.name}/#.#.#' for module.`, + `Invalid tag format: '${tag}'. Expected format: '${this.name}[separator]v#.#.#' or '${this.name}[separator]#.#.#'.`, ); } - // Extract everything after the last slash - const versionPart = tag.substring(tag.lastIndexOf('/') + 1); - - // Validate that the version part matches the expected format - if (!VERSION_TAG_REGEX.test(versionPart)) { + // Parse the tag using MODULE_TAG_REGEX to extract version components + // Note: This will never be null since TerraformModule.isModuleAssociatedWithTag already checks for this; + // however, for typing, we'll just recheck. + const match = MODULE_TAG_REGEX.exec(tag); + /* v8 ignore next 5 */ + if (!match) { throw new Error( - `Invalid tag format: '${tag}'. Expected format: '${this.name}/v#.#.#' or '${this.name}/#.#.#' for module.`, + `Invalid tag format: '${tag}'. Expected format: '${this.name}[separator]v#.#.#' or '${this.name}[separator]#.#.#'.`, ); } - // Return only the numerical part, stripping the 'v' prefix if present - return versionPart.startsWith('v') ? versionPart.substring(1) : versionPart; + // Extract the numerical version components (groups 4, 5, 6 are major.minor.patch) + const major = match[4]; + const minor = match[5]; + const patch = match[6]; + + return `${major}.${minor}.${patch}`; } /** @@ -614,50 +630,68 @@ export class TerraformModule { * * The function transforms the directory path by: * - Trimming whitespace - * - Replacing invalid characters with hyphens - * - Normalizing slashes - * - Removing leading/trailing slashes - * - Handling consecutive dots and hyphens - * - Removing any remaining whitespace * - Converting to lowercase (for consistency) - * - Removing trailing dots, hyphens, and underscores + * - Normalizing path separators (both backslashes and forward slashes) to the configured tag directory separator + * - Replacing invalid characters with hyphens (preserving only alphanumeric, "/", ".", "-", "_") + * - Normalizing consecutive special characters ("/", ".", "-", "_") to single instances + * - Removing leading/trailing special characters ("/", ".", "-", "_") using safe string operations * * @param {string} terraformDirectory - The relative directory path from which to generate the module name. * @returns {string} A valid Terraform module name based on the provided directory path. */ public static getTerraformModuleNameFromRelativePath(terraformDirectory: string): string { - const cleanedDirectory = terraformDirectory + let name = terraformDirectory .trim() - .replace(/[^a-zA-Z0-9/_-]+/g, '-') - .replace(/\/{2,}/g, '/') - .replace(/\/\.+/g, '/') - .replace(/(^\/|\/$)/g, '') - .replace(/\.\.+/g, '.') - .replace(/--+/g, '-') - .replace(/\s+/g, '') - .toLowerCase(); - return removeTrailingCharacters(cleanedDirectory, ['.', '-', '_']); + .toLowerCase() + .replace(/[/\\]/g, config.tagDirectorySeparator) // Normalize backslashes and forward slashes to configured separator + .replace(/[^a-zA-Z0-9/._-]+/g, '-') // Replace invalid characters with hyphens (preserve alphanumeric, /, ., _, -) + .replace(/[/._-]{2,}/g, (match) => match[0]); // Normalize consecutive special characters to single instances + + // Remove leading/trailing special characters safely without regex backtracking + name = removeLeadingCharacters(name, VALID_TAG_DIRECTORY_SEPARATORS); + name = removeTrailingCharacters(name, VALID_TAG_DIRECTORY_SEPARATORS); + + return name; } /** * Static utility to check if a tag is associated with a given module name. - * Supports both versioned tags ({moduleName}/v#.#.#) and non-versioned tags ({moduleName}/#.#.#). + * Supports multiple directory separators and handles cases where tagging schemes + * may have changed over time (e.g., from 'module-name/v1.0.0' to 'module-name-v1.1.0'). * - * @param {string} moduleName - The Terraform module name + * @param {string} moduleName - The Terraform module name (assumed to be cleaned) * @param {string} tag - The tag to check * @returns {boolean} True if the tag belongs to the module and has valid version format */ public static isModuleAssociatedWithTag(moduleName: string, tag: string): boolean { - // Check if tag starts with exactly the module name followed by a slash - if (!tag.startsWith(`${moduleName}/`)) { + // Use the existing MODULE_TAG_REGEX to parse the tag and extract module name + version + const match = MODULE_TAG_REGEX.exec(tag); + if (!match) { + // The tag doesn't match the expected "module-name/version" format return false; } - // Extract the version part after the module name and slash - const versionPart = tag.substring(moduleName.length + 1); + // Extract the module name part from the tag (group 1) + const moduleNameFromTag = match[1]; + + // Define a consistent separator to normalize module names + const NORMALIZE_SEPARATOR = '|'; + + // Normalize both the input moduleName and the extracted module name from the tag. + // This allows for comparison even if the tagging scheme changed over time + // (e.g., from 'my/module/v1.0.0' to 'my-module-v1.1.0'). + const normalizeName = (name: string): string => { + // Replace all valid tag directory separators with a consistent separator + // This handles cases where different separators were used in different tags + let normalized = name; + for (const separator of VALID_TAG_DIRECTORY_SEPARATORS) { + normalized = normalized.replaceAll(separator, NORMALIZE_SEPARATOR); + } + return normalized; + }; - // Check if version part matches either v#.#.# or #.#.# format - return VERSION_TAG_REGEX.test(versionPart); + // Compare the normalized names to determine if they match + return normalizeName(moduleName) === normalizeName(moduleNameFromTag); } /** @@ -696,8 +730,9 @@ export class TerraformModule { * Determines an array of Terraform tags that need to be deleted. * * Identifies tags that belong to modules no longer present in the current - * module list by filtering tags that match the pattern {moduleName}/vX.Y.Z - * where the module name is not in the current modules. + * module list by checking if any current module is associated with each tag. + * This approach leverages the robust tag association logic that handles + * different separator schemes over time. * * @param {string[]} allTags - A list of all tags associated with the modules. * @param {TerraformModule[]} terraformModules - An array of Terraform modules. @@ -706,16 +741,12 @@ export class TerraformModule { public static getTagsToDelete(allTags: string[], terraformModules: TerraformModule[]): string[] { startGroup('Finding all Terraform tags that should be deleted'); - // Get module names from current terraformModules (these exist in source) - const moduleNamesFromModules = new Set(terraformModules.map((module) => module.name)); - - // Filter tags that belong to modules no longer in the current module list + // Filter tags that are not associated with any current module const tagsToRemove = allTags .filter((tag) => { - // Extract the Terraform module name from tag by removing the version suffix - const match = MODULE_TAG_REGEX.exec(tag); - const moduleName = match ? match[1] : tag; - return !moduleNamesFromModules.has(moduleName); + // Check if ANY current module is associated with this tag + // This handles cases where tagging schemes changed over time + return !terraformModules.some((module) => TerraformModule.isModuleAssociatedWithTag(module.name, tag)); }) .sort((a, b) => a.localeCompare(b)); @@ -731,8 +762,9 @@ export class TerraformModule { * Determines an array of Terraform releases that need to be deleted. * * Identifies releases that belong to modules no longer present in the current - * module list by filtering releases that match the pattern {moduleName}/vX.Y.Z - * where the module name is not in the current modules. + * module list by checking if any current module is associated with each release tag. + * This approach leverages the robust tag association logic that handles + * different separator schemes over time. * * @param {GitHubRelease[]} allReleases - A list of all releases associated with the modules. * @param {TerraformModule[]} terraformModules - An array of Terraform modules. @@ -749,16 +781,14 @@ export class TerraformModule { ): GitHubRelease[] { startGroup('Finding all Terraform releases that should be deleted'); - // Get module names from current terraformModules (these exist in source) - const moduleNamesFromModules = new Set(terraformModules.map((module) => module.name)); - - // Filter releases that belong to modules no longer in the current module list + // Filter releases that are not associated with any current module const releasesToRemove = allReleases .filter((release) => { - // Extract module name from versioned release tag - const match = MODULE_TAG_REGEX.exec(release.tagName); - const moduleName = match ? match[1] : release.tagName; - return !moduleNamesFromModules.has(moduleName); + // Check if ANY current module is associated with this release tag + // This handles cases where tagging schemes changed over time + return !terraformModules.some((module) => + TerraformModule.isModuleAssociatedWithTag(module.name, release.tagName), + ); }) .sort((a, b) => a.tagName.localeCompare(b.tagName)); diff --git a/src/types/config.types.ts b/src/types/config.types.ts index 32543a88..06332070 100644 --- a/src/types/config.types.ts +++ b/src/types/config.types.ts @@ -26,7 +26,8 @@ export interface Config { /** * Default first tag for initializing repositories without existing tags. - * This serves as the fallback tag when no tags are found in the repository. + * This serves as the fallback tag when no tags are found in the repository. Note this may + * be in the format of `v#.#.#` or `#.#.#` (e.g., `v1.0.0` or `1.0.0`). */ defaultFirstTag: string; @@ -52,6 +53,17 @@ export interface Config { */ wikiSidebarChangelogMax: number; + /** + * A raw, multi-line string to override the default 'Usage' section in the generated wiki. + * If not provided, a default usage block will be generated. Supports template variables like: + * - {{module_name}}: The name of the module + * - {{latest_tag}}: The latest git tag for the module + * - {{latest_tag_version_number}}: The version number from the latest tag + * - {{module_source}}: The source URL for the module + * - {{module_name_terraform}}: The module name formatted for Terraform usage (alphanumeric and underscores only) + */ + wikiUsageTemplate: string; + /** * Flag to control whether the small branding link should be disabled or not in the * pull request (PR) comments. When branding is enabled, a link to the action's @@ -67,6 +79,13 @@ export interface Config { */ githubToken: string; + /** + * A list of module paths to completely ignore when processing. Any module whose path matches + * one of these patterns will not be processed for versioning, release, or documentation. + * Paths are relative to the workspace directory. + */ + modulePathIgnore: string[]; + /** * A comma-separated list of file patterns to exclude from triggering version changes in Terraform modules. * These patterns follow glob syntax (e.g., ".gitignore,*.md") and are relative to each Terraform module directory within @@ -97,15 +116,38 @@ export interface Config { useSSHSourceFormat: boolean; /** - * A list of module paths to completely ignore when processing. Any module whose path matches - * one of these patterns will not be processed for versioning, release, or documentation. - * Paths are relative to the workspace directory. + * The character used to separate directory path components when creating Git tags from module paths. + * This separator is applied throughout the entire directory structure conversion process, not just + * between the module name and version. + * + * Must be a single character and one of: -, _, /, . + * + * When converting a module path like 'modules/aws/s3-bucket' to a Git tag, this separator determines + * how directory separators (/) are replaced in the tag name portion: + * + * Examples with module path 'modules/aws/s3-bucket' and version 'v1.0.0': + * - "/" (default): modules/aws/s3-bucket/v1.0.0 + * - "-": modules-aws-s3-bucket-v1.0.0 + * - "_": modules_aws_s3_bucket_v1.0.0 + * - ".": modules.aws.s3-bucket.v1.0.0 + * + * This setting affects tag creation, tag parsing, and tag association logic throughout the system. */ - modulePathIgnore: string[]; + tagDirectorySeparator: string; /** - * A raw, multi-line string to override the default 'Usage' section in the generated wiki. - * If not provided, a default usage block will be generated. + * Whether to include the "v" prefix in version tags. + * + * When true (default), version tags will include the "v" prefix: + * - Example: module/v1.2.3 + * + * When false, version tags will not include the "v" prefix: + * - Example: module/1.2.3 + * + * For initial releases, this setting takes precedence over any "v" prefix specified in the + * defaultFirstTag configuration. If useVersionPrefix is false and defaultFirstTag contains + * a "v" prefix (e.g., "v1.0.0"), the "v" will be automatically removed to ensure consistency + * with the useVersionPrefix setting (resulting in "1.0.0"). */ - wikiUsageTemplate?: string; + useVersionPrefix: boolean; } diff --git a/src/types/context.types.ts b/src/types/context.types.ts index fa1c805a..9528f304 100644 --- a/src/types/context.types.ts +++ b/src/types/context.types.ts @@ -1,4 +1,4 @@ -import type { OctokitRestApi, Repo } from './github.types'; +import type { OctokitRestApi, Repo } from '@/types/github.types'; /** * Context and runtime related types diff --git a/src/types/index.ts b/src/types/index.ts index 1cd04a7b..dd55ef10 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,3 +1,6 @@ +// Action metadata types +export * from './metadata.types'; + // Common types export * from './common.types'; diff --git a/src/types/metadata.types.ts b/src/types/metadata.types.ts new file mode 100644 index 00000000..4ebfff8e --- /dev/null +++ b/src/types/metadata.types.ts @@ -0,0 +1,41 @@ +import type { Config } from '@/types/config.types'; + +/** + * Metadata definition for GitHub Action inputs that enables dynamic configuration mapping. + * + * This interface serves as the translation layer between GitHub Action inputs defined in + * action.yml and our internal Config type. It provides the necessary metadata to: + * - Parse input values according to their expected types + * - Map action inputs to the corresponding config property names + * - Enforce required/optional input validation + * - Support dynamic config creation in createConfigFromInputs() + * + * The metadata is used by the ACTION_INPUTS constant in metadata.ts to create a + * comprehensive mapping of all action inputs, which then drives the automatic + * config generation process. + * + * @see {@link /workspaces/terraform-module-releaser/src/utils/metadata.ts} for usage + * @see {@link https://docs.github.com/en/actions/reference/metadata-syntax-for-github-actions#inputs} GitHub Actions input reference + */ +export interface ActionInputMetadata { + /** + * The config property name this input maps to. + * Must be a valid key from the Config interface. + */ + configKey: keyof Config; + + /** + * Whether this input is required by the GitHub Action. + * When true, the action will fail if the input is not provided. + */ + required: boolean; + + /** + * The expected data type of the input for proper parsing and validation. + * - 'string': Direct string value + * - 'boolean': Parsed using getBooleanInput for proper true/false handling + * - 'number': Parsed using parseInt for integer conversion + * - 'array': Comma-separated string parsed into array with deduplication + */ + type: 'string' | 'boolean' | 'number' | 'array'; +} diff --git a/src/types/wiki.types.ts b/src/types/wiki.types.ts index 2377b2c8..5a554276 100644 --- a/src/types/wiki.types.ts +++ b/src/types/wiki.types.ts @@ -1,5 +1,5 @@ +import type { ExecSyncError } from '@/types/node-child-process.types'; import type { WIKI_STATUS } from '@/utils/constants'; -import type { ExecSyncError } from './node-child-process.types'; /** * Represents the status of wiki operations. diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 922f45dc..7ed67192 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1,3 +1,19 @@ +/** + * Defines valid separator characters for tag directory paths in Terraform module releases. + * + * When finding a Terraform module like `modules/aws/s3-bucket`, the release tag would typically be + * `modules/aws/s3-bucket/v1.0.0`. This constant allows for alternative separators in the tag path. + * + * For example, with these separators, the following tag formats would all be valid: + * - `modules/aws/s3-bucket/v1.0.0` (using '/') + * - `modules-aws-s3-bucket-v1.0.0` (using '-') + * - `modules_aws_s3_bucket_v1.0.0` (using '_') + * - `modules.aws.s3.bucket.v1.0.0` (using '.') + * + * The default separator is '/' as defined in action.yml.dddd + */ +export const VALID_TAG_DIRECTORY_SEPARATORS = ['-', '_', '/', '.']; + /** * Regular expression that matches version tags in the format of semantic versioning. * This regex validates version strings like "1.2.3" or "v1.2.3" and includes capture groups. @@ -8,13 +24,32 @@ * It allows either a numerical portion (e.g., "1.2.3") or one prefixed with 'v' (e.g., "v1.2.3"), * which is the proper semver default format. */ -export const VERSION_TAG_REGEX = /^v?(\d+)\.(\d+)\.(\d+)$/; +export const VERSION_TAG_REGEX = /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/; /** * Matches a Terraform module tag in the format: module-name/v1.2.3 or module-name/1.2.3 * Group 1: module name, Group 2: version (with or without 'v' prefix) */ -export const MODULE_TAG_REGEX = /^(.+)\/(v?\d+\.\d+\.\d+)$/; +/** + * Regular expression pattern to match a module tag in the format: prefix + separator + version + * Where: + * - Group 1: prefix (e.g., "module", "feature") + * - Group 2: separator (one of: '-', '_', '/', '.') + * - Group 3: Complete version string with optional 'v' prefix (e.g., "v1.0.0", "1.0.0") + * - Group 4: Major version number + * - Group 5: Minor version number + * - Group 6: Patch version number + * + * Example matches: + * - "module-v1.0.0" → ["module-v1.0.0", "module", "-", "v1.0.0", "1", "0", "0"] + * - "feature_2.3.4" → ["feature_2.3.4", "feature", "_", "2.3.4", "2", "3", "4"] + * - "service/v0.1.0" → ["service/v0.1.0", "service", "/", "v0.1.0", "0", "1", "0"] + * + * Note: In the character class [-_/.], only the dot (.) requires escaping to match literal periods. + * The hyphen (-) doesn't need escaping when at the start/end of the character class. + * The forward slash (/) doesn't need escaping in JavaScript regex character classes. + */ +export const MODULE_TAG_REGEX = /^(.+)([-_/.])(v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*))$/; /** * Release type constants for semantic versioning diff --git a/src/utils/metadata.ts b/src/utils/metadata.ts new file mode 100644 index 00000000..07c32530 --- /dev/null +++ b/src/utils/metadata.ts @@ -0,0 +1,115 @@ +import type { ActionInputMetadata, Config } from '@/types'; +import { getBooleanInput, getInput } from '@actions/core'; + +/** + * Factory functions to reduce duplication in ACTION_INPUTS metadata definitions. + * These functions create standardized metadata objects for common input patterns. + */ +const requiredString = (configKey: keyof Config): ActionInputMetadata => ({ + configKey, + required: true, + type: 'string', +}); + +const requiredBoolean = (configKey: keyof Config): ActionInputMetadata => ({ + configKey, + required: true, + type: 'boolean', +}); + +const requiredArray = (configKey: keyof Config): ActionInputMetadata => ({ + configKey, + required: true, + type: 'array', +}); + +const requiredNumber = (configKey: keyof Config): ActionInputMetadata => ({ + configKey, + required: true, + type: 'number', +}); + +const optionalArray = (configKey: keyof Config): ActionInputMetadata => ({ + configKey, + required: false, + type: 'array', +}); + +/** + * Complete mapping of all GitHub Action inputs to their metadata. + * This is the single source of truth for input configuration. + * Note: defaultValue is removed as defaults come from action.yml at runtime + */ +export const ACTION_INPUTS: Record = { + 'major-keywords': requiredArray('majorKeywords'), + 'minor-keywords': requiredArray('minorKeywords'), + 'patch-keywords': requiredArray('patchKeywords'), + 'default-first-tag': requiredString('defaultFirstTag'), + 'terraform-docs-version': requiredString('terraformDocsVersion'), + 'delete-legacy-tags': requiredBoolean('deleteLegacyTags'), + 'disable-wiki': requiredBoolean('disableWiki'), + 'wiki-sidebar-changelog-max': requiredNumber('wikiSidebarChangelogMax'), + 'wiki-usage-template': requiredString('wikiUsageTemplate'), + 'disable-branding': requiredBoolean('disableBranding'), + 'module-path-ignore': optionalArray('modulePathIgnore'), + 'module-change-exclude-patterns': optionalArray('moduleChangeExcludePatterns'), + 'module-asset-exclude-patterns': optionalArray('moduleAssetExcludePatterns'), + 'use-ssh-source-format': requiredBoolean('useSSHSourceFormat'), + github_token: requiredString('githubToken'), + 'tag-directory-separator': requiredString('tagDirectorySeparator'), + 'use-version-prefix': requiredBoolean('useVersionPrefix'), +} as const; + +/** + * Creates a config object by reading inputs using GitHub Actions API and converting them + * according to the metadata definitions. This provides a dynamic way to build the config + * without manually mapping each input. + */ +export function createConfigFromInputs(): Config { + const config = {} as Config; + + for (const [inputName, metadata] of Object.entries(ACTION_INPUTS)) { + const { configKey, required, type } = metadata; + + try { + let value: unknown; + + if (type === 'boolean') { + // Use getBooleanInput for boolean types for proper parsing + value = getBooleanInput(inputName, { required }); + } else if (type === 'array') { + // Handle array inputs with special parsing + const input = getInput(inputName, { required }); + + if (!input || input.trim() === '') { + value = []; + } else { + value = Array.from( + new Set( + input + .split(',') + .map((item: string) => item.trim()) + .filter(Boolean), + ), + ); + } + } else if (type === 'number') { + // Handle number inputs with parseInt + const input = getInput(inputName, { required }); + value = Number.parseInt(input, 10); + } else { + // Handle string inputs + value = getInput(inputName, { required }); + } + + // Safely assign to config using the configKey + Object.assign(config, { [configKey]: value }); + } catch (error) { + throw new Error( + `Failed to process input '${inputName}': ${error instanceof Error ? error.message : String(error)}`, + ); + } + } + + return config; +} diff --git a/src/utils/string.ts b/src/utils/string.ts index 95e88172..6b0a7546 100644 --- a/src/utils/string.ts +++ b/src/utils/string.ts @@ -1,35 +1,3 @@ -/** - * Removes any leading and trailing slashes (/) from the given string. - * - * @param {string} str - The input string from which to trim slashes. - * @returns {string} - The string without leading or trailing slashes. - * - * @example - * // Returns "example/path" - * trimSlashes("/example/path/"); - * - * @example - * // Returns "another/example" - * trimSlashes("///another/example///"); - */ -export function trimSlashes(str: string): string { - let start = 0; - let end = str.length; - - // Remove leading slashes by adjusting start index - while (start < end && str[start] === '/') { - start++; - } - - // Remove trailing slashes by adjusting end index - while (end > start && str[end - 1] === '/') { - end--; - } - - // Return the substring without leading and trailing slashes - return str.slice(start, end); -} - /** * Removes trailing characters from a string without using regex. * @@ -55,5 +23,56 @@ export function removeTrailingCharacters(input: string, charactersToRemove: stri while (endIndex > 0 && charactersToRemove.includes(input[endIndex - 1])) { endIndex--; } + return input.slice(0, endIndex); } + +/** + * Removes leading characters from a string without using regex. + * + * This function iteratively checks each character from the beginning of the string + * and removes any consecutive characters that match the specified characters to remove. + * It uses a direct character-by-character approach instead of regex to avoid potential + * backtracking issues and ensure consistent O(n) performance. + * + * @param {string} input - The string to process + * @param {string[]} charactersToRemove - Array of characters to remove from the beginning + * @returns {string} The input string with all leading specified characters removed + * + * @example + * // Returns "example" + * removeLeadingCharacters("...example", ["."]) + * + * @example + * // Returns "module-name" + * removeLeadingCharacters("._-module-name", [".", "-", "_"]) + */ +export function removeLeadingCharacters(input: string, charactersToRemove: string[]): string { + let startIndex = 0; + while (startIndex < input.length && charactersToRemove.includes(input[startIndex])) { + startIndex++; + } + + return input.slice(startIndex); +} + +/** + * Renders a template string by replacing placeholders with provided values. + * + * @param template The template string containing placeholders in the format `{{key}}`. + * @param variables An object where keys correspond to placeholder names and values are their replacements. + * @returns The rendered string with placeholders replaced. + * + * @example + * // Returns "Hello, World!" + * renderTemplate("Hello, {{name}}!", { name: "World" }) + * + * @example + * // Returns "Hi, There!" + * renderTemplate("{{greeting}}, {{name}}!", { greeting: "Hi", name: "There" }) + */ +export function renderTemplate(template: string, variables: Record): string { + return template.replace(/\{\{(\w+)\}\}/g, (placeholder, key) => { + return key in variables ? variables[key] : placeholder; + }); +} diff --git a/src/wiki.ts b/src/wiki.ts index 49ba5f4c..19c08af9 100644 --- a/src/wiki.ts +++ b/src/wiki.ts @@ -7,7 +7,7 @@ import { join, resolve } from 'node:path'; import { getTerraformModuleFullReleaseChangelog } from '@/changelog'; import { config } from '@/config'; import { context } from '@/context'; -import { render } from '@/templating'; +import { renderTemplate } from '@/utils/string'; import { generateTerraformDocs } from '@/terraform-docs'; import type { TerraformModule } from '@/terraform-module'; import type { ExecSyncError, WikiStatusResult } from '@/types'; @@ -306,10 +306,10 @@ async function generateWikiTerraformModule(terraformModule: TerraformModule): Pr const tfDocs = await generateTerraformDocs(terraformModule); const moduleSource = getModuleSource(context.repoUrl, config.useSSHSourceFormat); - const usage = render(config.wikiUsageTemplate, { + const usage = renderTemplate(config.wikiUsageTemplate, { module_name: terraformModule.name, - latest_tag: terraformModule.getLatestTag(), - latest_tag_version_number: terraformModule.getLatestTagVersionNumber(), + latest_tag: terraformModule.getLatestTag() ?? '', + latest_tag_version_number: terraformModule.getLatestTagVersionNumber() ?? '', module_source: moduleSource, module_name_terraform: terraformModule.name.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase(), });