├── .yarn ├── versions │ ├── 7e96cfb7.yml │ ├── 990a22e4.yml │ ├── b993d524.yml │ └── d7dbca40.yml └── plugins │ └── @yarnpkg │ └── plugin-outdated.cjs ├── .tool-versions ├── .github ├── CODEOWNERS ├── FUNDING.yml ├── workflows │ ├── release.yml │ ├── update-license.yml │ ├── sync-labels.yml │ ├── linting.yml │ ├── unit-tests.yml │ ├── security.yml │ ├── test-source.yml │ └── test-providers.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── pull_request_template.md ├── .markdownlintignore ├── .gitattributes ├── src ├── __tests__ │ ├── text.txt │ ├── extract.spec.ts │ └── providers │ │ ├── LinguaToolsProvider.spec.ts │ │ ├── DeeplProvider.spec.ts │ │ ├── FunTranslationsProvider.spec.ts │ │ ├── GoogleProvider.spec.ts │ │ ├── MyMemoryProvider.spec.ts │ │ └── ProviderTester.ts ├── extract.ts ├── providers │ ├── GoogleProvider.ts │ ├── DeeplProvider.ts │ ├── LinguaToolsProvider.ts │ ├── MyMemoryProvider.ts │ ├── FunTranslationsProvider.ts │ ├── LibreTranslateProvider.ts │ ├── MicrosoftProvider.ts │ ├── ProviderFactory.ts │ └── ProviderBase.ts └── index.ts ├── .gitleaks.toml ├── .codeclimate.yml ├── .markdownlint.yml ├── .yamllint.yml ├── .yarnrc.yml ├── .gitignore ├── tsconfig.json ├── Makefile ├── jest.config.json ├── action.yml ├── LICENSE ├── biome.json ├── package.json ├── CONTRIBUTING.md ├── .pre-commit-config.yaml └── README.md /.yarn/versions/7e96cfb7.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.yarn/versions/990a22e4.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.yarn/versions/b993d524.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.yarn/versions/d7dbca40.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 25.2.1 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @fabasoad 2 | -------------------------------------------------------------------------------- /.markdownlintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | dist/** linguist-generated=true 2 | -------------------------------------------------------------------------------- /src/__tests__/text.txt: -------------------------------------------------------------------------------- 1 | Simple text for translation. 2 | -------------------------------------------------------------------------------- /.gitleaks.toml: -------------------------------------------------------------------------------- 1 | [allowlist] 2 | paths = [ 3 | '''.yarn/.*''' 4 | ] 5 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: "2" 3 | checks: 4 | method-complexity: 5 | config: 6 | threshold: 6 7 | -------------------------------------------------------------------------------- /.markdownlint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | default: true 3 | MD013: 4 | code_blocks: false 5 | tables: false 6 | MD041: false 7 | -------------------------------------------------------------------------------- /.yamllint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | 4 | rules: 5 | line-length: 6 | max: 165 7 | level: error 8 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | plugins: 4 | - path: .yarn/plugins/@yarnpkg/plugin-outdated.cjs 5 | spec: "https://mskelton.dev/yarn-outdated/v2" 6 | 7 | yarnPath: .yarn/releases/yarn-4.12.0.cjs 8 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | --- 2 | custom: 3 | [ 4 | "https://www.bitcoinqrcodemaker.com/?style=bitcoin&address=145HwyQAcv4vrzUumJhu7nWGAVBysX9jJH&prefix=on", 5 | "https://paypal.me/fabasoad", 6 | ] 7 | github: ["fabasoad"] 8 | ko_fi: fabasoad 9 | liberapay: fabasoad 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output 2 | .vscode/* 3 | !.vscode/settings.json 4 | .env 5 | .idea 6 | coverage 7 | node_modules 8 | yarn-error.log 9 | .pnp.* 10 | .yarn/* 11 | !.yarn/patches 12 | !.yarn/plugins 13 | !.yarn/releases 14 | !.yarn/sdks 15 | !.yarn/versions 16 | 17 | # jest 18 | jest-report.json 19 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release 3 | 4 | on: # yamllint disable-line rule:truthy 5 | push: 6 | tags: 7 | - "v*.*.*" 8 | 9 | jobs: 10 | github: 11 | name: GitHub 12 | uses: fabasoad/reusable-workflows/.github/workflows/wf-github-release.yml@main 13 | permissions: 14 | contents: write 15 | -------------------------------------------------------------------------------- /.github/workflows/update-license.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: License 3 | 4 | on: # yamllint disable-line rule:truthy 5 | schedule: 6 | # Every January 1st at 14:00 JST 7 | - cron: "0 5 1 1 *" 8 | 9 | jobs: 10 | maintenance: 11 | name: Maintenance 12 | uses: fabasoad/reusable-workflows/.github/workflows/wf-update-license.yml@main 13 | permissions: 14 | contents: write 15 | pull-requests: write 16 | -------------------------------------------------------------------------------- /.github/workflows/sync-labels.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Labels 3 | 4 | on: # yamllint disable-line rule:truthy 5 | push: 6 | branches: 7 | - main 8 | workflow_dispatch: {} 9 | 10 | jobs: 11 | maintenance: 12 | name: Maintenance 13 | uses: fabasoad/reusable-workflows/.github/workflows/wf-sync-labels.yml@main 14 | permissions: 15 | contents: write 16 | issues: write 17 | pull-requests: write 18 | -------------------------------------------------------------------------------- /src/__tests__/extract.spec.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import extract from '../extract'; 3 | 4 | describe('extract', () => { 5 | test('should extract text', () => { 6 | const text = 'some text'; 7 | expect(extract(text)).toEqual(text); 8 | }); 9 | 10 | test('should extract file content', () => { 11 | expect(extract(path.join(__dirname, 'text.txt'))) 12 | .toEqual('Simple text for translation.'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2024", 4 | "module": "commonjs", 5 | "lib": [ 6 | "esnext" 7 | ], 8 | "baseUrl": "./", 9 | "paths": { 10 | "*": ["src/types/*"] 11 | }, 12 | "moduleResolution": "node", 13 | "outDir": "./lib", 14 | "rootDir": "./src", 15 | "strict": true, 16 | "noImplicitAny": false, 17 | "esModuleInterop": true 18 | }, 19 | "exclude": ["node_modules", "src/__tests__"] 20 | } 21 | -------------------------------------------------------------------------------- /src/extract.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import { debug } from '@actions/core' 3 | 4 | const extract = (source: string): string => { 5 | if (fs.existsSync(source)) { 6 | debug(`${source} file exists. Reading from file...`) 7 | const text: string = fs.readFileSync(source, 'utf-8').trim() 8 | debug(`File content: ${text}`) 9 | return text 10 | } 11 | debug(`${source} file does not exist. "${source}" will be translated`) 12 | return source 13 | } 14 | 15 | export default extract 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: audit build clean install reinstall lint test outdated upgrade 2 | 3 | .DEFAULT_GOAL := build 4 | 5 | audit: 6 | @yarn npm audit --all 7 | 8 | build: 9 | @yarn run build 10 | 11 | clean: 12 | @rm -f yarn.lock 13 | @rm -rf node_modules 14 | 15 | install: 16 | @yarn install 17 | 18 | reinstall: clean install 19 | 20 | lint: 21 | @yarn run lint 22 | 23 | test: 24 | @yarn run test 25 | 26 | outdated: 27 | @yarn outdated 28 | 29 | upgrade: 30 | @pre-commit autoupdate 31 | @yarn upgrade-interactive 32 | -------------------------------------------------------------------------------- /.github/workflows/linting.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Linting 3 | 4 | on: # yamllint disable-line rule:truthy 5 | pull_request: {} 6 | push: 7 | branches: 8 | - main 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | js-lint: 15 | name: JS Lint 16 | uses: fabasoad/reusable-workflows/.github/workflows/wf-js-lint.yml@main 17 | pre-commit: 18 | name: Pre-commit 19 | uses: fabasoad/reusable-workflows/.github/workflows/wf-pre-commit.yml@main 20 | with: 21 | skip-hooks: "audit, build, lint, test" 22 | -------------------------------------------------------------------------------- /.github/workflows/unit-tests.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Unit Tests 3 | 4 | on: # yamllint disable-line rule:truthy 5 | pull_request: 6 | paths: 7 | - dist/** 8 | - src/** 9 | - jest.config.json 10 | push: 11 | branches: 12 | - main 13 | workflow_dispatch: {} 14 | 15 | defaults: 16 | run: 17 | shell: sh 18 | 19 | jobs: 20 | jest: 21 | name: Jest 22 | uses: fabasoad/reusable-workflows/.github/workflows/wf-js-unit-tests.yml@main 23 | permissions: 24 | contents: read 25 | secrets: inherit # pragma: allowlist secret 26 | -------------------------------------------------------------------------------- /src/__tests__/providers/LinguaToolsProvider.spec.ts: -------------------------------------------------------------------------------- 1 | import LinguaToolsProvider from '../../providers/LinguaToolsProvider' 2 | import ProviderTester from './ProviderTester' 3 | 4 | describe('LinguaToolsProvider', () => { 5 | let providerTester: ProviderTester 6 | 7 | beforeAll(() => { 8 | providerTester = new ProviderTester( 9 | new LinguaToolsProvider() 10 | ) 11 | }) 12 | 13 | test.skip( 14 | 'should get correct translation', 15 | async () => providerTester.positive() 16 | ) 17 | 18 | test.skip( 19 | 'should fail because of invalid lang', 20 | async () => providerTester.negative() 21 | ) 22 | }) 23 | -------------------------------------------------------------------------------- /src/__tests__/providers/DeeplProvider.spec.ts: -------------------------------------------------------------------------------- 1 | import DeeplProvider from '../../providers/DeeplProvider' 2 | import { config } from 'dotenv' 3 | import ProviderTester from './ProviderTester' 4 | 5 | config() 6 | 7 | describe('DeeplProvider', () => { 8 | let providerTester: ProviderTester 9 | 10 | beforeAll(() => { 11 | providerTester = new ProviderTester( 12 | new DeeplProvider(process.env.DEEPL_API_KEY || '') 13 | ) 14 | }) 15 | 16 | test( 17 | 'should get correct translation', 18 | async () => providerTester.positive() 19 | ) 20 | 21 | test( 22 | 'should fail because of invalid lang', 23 | async () => providerTester.negative() 24 | ) 25 | }) 26 | -------------------------------------------------------------------------------- /src/__tests__/providers/FunTranslationsProvider.spec.ts: -------------------------------------------------------------------------------- 1 | import FunTranslationsProvider from '../../providers/FunTranslationsProvider' 2 | import ProviderTester from './ProviderTester' 3 | 4 | describe('FunTranslationsProvider', () => { 5 | let providerTester: ProviderTester 6 | 7 | beforeAll(() => { 8 | providerTester = new ProviderTester( 9 | new FunTranslationsProvider() 10 | ) 11 | }) 12 | 13 | test( 14 | 'should get correct translation', 15 | async () => providerTester.positive({ 16 | text: 'Evening', lang: 'vulcan', expected: 'Khru' 17 | }) 18 | ) 19 | 20 | test( 21 | 'should fail because of invalid lang', 22 | async () => providerTester.negative() 23 | ) 24 | }) 25 | -------------------------------------------------------------------------------- /jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "clearMocks": true, 3 | "collectCoverageFrom": [ 4 | "**/*.ts", 5 | "!**/*.d.ts", 6 | "!**/node_modules/**" 7 | ], 8 | "coverageReporters": [ 9 | "lcov", "text", "text-summary" 10 | ], 11 | "coverageThreshold": { 12 | "global": { 13 | "branches": 40, 14 | "functions": 55, 15 | "lines": 50, 16 | "statements": 50 17 | } 18 | }, 19 | "moduleFileExtensions": [ 20 | "js", 21 | "ts" 22 | ], 23 | "testEnvironment": "node", 24 | "testMatch": [ 25 | "**/*.spec.ts" 26 | ], 27 | "testRunner": "jest-circus/runner", 28 | "testTimeout": 20000, 29 | "transform": { 30 | "^.+\\.ts$": "ts-jest" 31 | }, 32 | "verbose": true 33 | } 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: fabasoad 7 | 8 | --- 9 | 10 | ## Is your feature request related to a problem? Please describe 11 | 12 | A clear and concise description of what the problem is. Ex. I'm always 13 | frustrated when [...] 14 | 15 | ## Describe the solution you'd like 16 | 17 | A clear and concise description of what you want to happen. 18 | 19 | ## Describe alternatives you've considered 20 | 21 | A clear and concise description of any alternative solutions or features you've 22 | considered. 23 | 24 | ## Additional context 25 | 26 | Add any other context or screenshots about the feature request here. 27 | -------------------------------------------------------------------------------- /src/providers/GoogleProvider.ts: -------------------------------------------------------------------------------- 1 | import ProviderBase from './ProviderBase' 2 | import { googleTranslateApi, translate } from 'google-translate-api-x' 3 | import fetch from 'cross-fetch' 4 | import TranslationResponse = googleTranslateApi.TranslationResponse 5 | 6 | export default class GoogleProvider extends ProviderBase { 7 | // biome-ignore lint/complexity/noUselessConstructor: ProviderFactory requires it 8 | constructor() { 9 | super() 10 | } 11 | 12 | async translate(text: string, lang: string): Promise { 13 | const [from, to]: string[] = lang.split('|') 14 | const response: TranslationResponse = await translate(text, { 15 | from, to, requestFunction: fetch 16 | }) 17 | return [response.text] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/__tests__/providers/GoogleProvider.spec.ts: -------------------------------------------------------------------------------- 1 | import GoogleProvider from '../../providers/GoogleProvider' 2 | import ProviderTester from './ProviderTester' 3 | 4 | describe('GoogleProvider', () => { 5 | let providerTester: ProviderTester 6 | 7 | beforeAll(() => { 8 | providerTester = new ProviderTester( 9 | new GoogleProvider() 10 | ) 11 | }) 12 | 13 | test( 14 | 'should get correct translation', 15 | async () => providerTester.positive({ 16 | text: 'Poem', 17 | lang: 'en|uk', 18 | expected: 'Вірш' 19 | }) 20 | ) 21 | 22 | test( 23 | 'should fail because of invalid lang', 24 | async () => providerTester.negative({ 25 | text: 'Anything', 26 | lang: 'en|abc123' 27 | }) 28 | ) 29 | }) 30 | -------------------------------------------------------------------------------- /src/providers/DeeplProvider.ts: -------------------------------------------------------------------------------- 1 | import ProviderBase from './ProviderBase' 2 | import { 3 | type SourceLanguageCode, 4 | type TargetLanguageCode, 5 | type TextResult, 6 | Translator 7 | } from 'deepl-node' 8 | 9 | export default class DeeplProvider extends ProviderBase { 10 | private translator: Translator 11 | 12 | constructor(apiKey: string) { 13 | super() 14 | this.translator = new Translator(apiKey) 15 | } 16 | 17 | async translate(text: string, lang: string): Promise { 18 | const l: string[] = lang.split('-') 19 | const result: TextResult = await this.translator.translateText( 20 | text, l[0] as SourceLanguageCode, l[1] as TargetLanguageCode 21 | ) 22 | return Promise.resolve([result.text]) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/providers/LinguaToolsProvider.ts: -------------------------------------------------------------------------------- 1 | import ProviderBase from './ProviderBase'; 2 | 3 | /* eslint-disable camelcase */ 4 | type LinguaToolsResponse = { freq: number, l1_text: string } 5 | 6 | export default class LinguaToolsProvider extends ProviderBase { 7 | constructor() { 8 | super('https://lt-translate-test.herokuapp.com') 9 | } 10 | 11 | translate(text: string, lang: string): Promise { 12 | const url = `/?langpair=${lang}&query=${text}` 13 | return this.api({ url, method: 'GET' }) 14 | .then((translations: LinguaToolsResponse[]) => { 15 | translations.sort((a: LinguaToolsResponse, b: LinguaToolsResponse) => a.freq > b.freq ? 1 : -1) 16 | return translations.map(({ l1_text }) => l1_text) 17 | }) 18 | } 19 | } 20 | /* eslint-enable camelcase */ 21 | -------------------------------------------------------------------------------- /src/providers/MyMemoryProvider.ts: -------------------------------------------------------------------------------- 1 | import ProviderBase from './ProviderBase'; 2 | 3 | type MyMemoryResponse = { matches: { match: number, translation: string }[] }; 4 | 5 | export default class MyMemoryProvider extends ProviderBase { 6 | private readonly apiKey: string | undefined; 7 | 8 | constructor(apiKey?: string) { 9 | super('https://api.mymemory.translated.net'); 10 | this.apiKey = apiKey; 11 | } 12 | 13 | async translate(text: string, lang: string): Promise { 14 | let url = `/get?q=${text}&langpair=${lang}`; 15 | url += (this.apiKey ? `&key=${this.apiKey}` : ''); 16 | const { matches }: MyMemoryResponse = await this.api({ url, method: 'GET' }) 17 | matches.sort((a, b) => a.match > b.match ? -1 : 1); 18 | return matches.map(({ translation }) => translation); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/providers/FunTranslationsProvider.ts: -------------------------------------------------------------------------------- 1 | import ProviderBase from './ProviderBase' 2 | 3 | type FunTranslationsResponse = 4 | { success: { total: number } | undefined, contents: { translated: string } } 5 | 6 | export default class FunTranslationsProvider extends ProviderBase { 7 | constructor() { 8 | super('https://api.funtranslations.com') 9 | } 10 | 11 | async translate(text: string, lang: string): Promise { 12 | const url = `/translate/${lang}.json?text=${text}` 13 | const { success, contents }: FunTranslationsResponse = 14 | await this.api({ url, method: 'GET' }) 15 | if (success && success.total > 0) { 16 | return [contents.translated] 17 | } 18 | console.warn( 19 | 'Result is either not success or doesn\'t have any translations') 20 | return [text] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/providers/LibreTranslateProvider.ts: -------------------------------------------------------------------------------- 1 | import ProviderBase from './ProviderBase' 2 | 3 | /* eslint-disable camelcase */ 4 | type LibreTranslateResponse = { translatedText: string } 5 | 6 | export default class LibreTranslateProvider extends ProviderBase { 7 | private apiKey: string 8 | 9 | constructor(apiKey: string) { 10 | super('https://libretranslate.com/translate') 11 | this.apiKey = apiKey 12 | } 13 | 14 | async translate(text: string, lang: string): Promise { 15 | const [source, target] = lang.split('-', 2) 16 | const data = { 17 | q: text, 18 | source, 19 | target, 20 | format: 'text', 21 | api_key: this.apiKey 22 | } 23 | const { translatedText }: LibreTranslateResponse = 24 | await this.api({ data, url: '/', method: 'POST' }) 25 | return [translatedText] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/security.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Security 3 | 4 | on: # yamllint disable-line rule:truthy 5 | pull_request: {} 6 | push: 7 | branches: 8 | - main 9 | workflow_dispatch: 10 | inputs: 11 | security-type: 12 | description: What Security scanning you would like to run? 13 | required: false 14 | default: "all" 15 | type: choice 16 | options: ["all", "sca", "code-scanning"] 17 | 18 | jobs: 19 | sast: 20 | name: SAST 21 | uses: fabasoad/reusable-workflows/.github/workflows/wf-security-sast.yml@main 22 | permissions: 23 | contents: read 24 | security-events: write 25 | with: 26 | code-scanning: ${{ contains(fromJSON('["all", "code-scanning"]'), github.event.inputs.security-type || 'all') }} 27 | sca: ${{ contains(fromJSON('["all", "sca"]'), github.event.inputs.security-type || 'all') }} 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: fabasoad 7 | 8 | --- 9 | 10 | ## Describe the bug 11 | 12 | A clear and concise description of what the bug is. 13 | 14 | ## Steps to Reproduce 15 | 16 | 1. Run '...' 17 | 2. See error 18 | 19 | ## Expected behavior 20 | 21 | A clear and concise description of what you expected to happen. 22 | 23 | ## Actual behavior 24 | 25 | A clear and concise description of what is happening now. 26 | 27 | ## Screenshots 28 | 29 | If applicable, add screenshots to help explain your problem. 30 | 31 | ## Technical information (please complete the following information) 32 | 33 | - OS: [e.g. Windows 10 Enterprise v.1909 (OS Build 18363.720)] 34 | - `translation-action` version [e.g. 1.4.1] 35 | 36 | ## Additional context 37 | 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /src/providers/MicrosoftProvider.ts: -------------------------------------------------------------------------------- 1 | import ProviderBase from './ProviderBase' 2 | 3 | type MicrosoftResponse = { translations: { text: string }[] } 4 | 5 | export default class MicrosoftProvider extends ProviderBase { 6 | private readonly apiKey: string 7 | private readonly addParam: string 8 | 9 | constructor(apiKey: string, addParam: string) { 10 | super('https://api.cognitive.microsofttranslator.com') 11 | this.apiKey = apiKey 12 | this.addParam = addParam 13 | } 14 | 15 | async translate(text: string, lang: string): Promise { 16 | const url = `/translate?api-version=3.0&to=${lang}` 17 | const response: MicrosoftResponse[] = await this.api({ 18 | url, 19 | headers: { 20 | 'ocp-apim-subscription-key': this.apiKey, 21 | 'ocp-apim-subscription-region': this.addParam, 22 | 'Content-Type': 'application/json' 23 | }, 24 | method: 'POST', 25 | data: { Text: text } 26 | }) 27 | return response[0].translations.map(({ text }) => text) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Translation Action" 3 | author: Yevhen Fabizhevskyi 4 | description: "This action translates any text to any language supported by chosen provider." 5 | branding: 6 | icon: users 7 | color: green 8 | inputs: 9 | source: 10 | description: | 11 | Text or path to the file (absolute or relative to $GITHUB_WORKSPACE) for 12 | translation. 13 | required: true 14 | provider: 15 | description: "Provider identifier." 16 | required: true 17 | api_key: 18 | description: "API key that should be used for chosen provider." 19 | required: false 20 | default: "" 21 | api_additional_parameter: 22 | description: | 23 | Additional parameter for the API. eg the region for Microsoft: canadacentral. 24 | required: false 25 | default: "" 26 | lang: 27 | description: | 28 | The translation direction. Should be one of the option proposed by chosen 29 | provider. 30 | required: true 31 | outputs: 32 | text: 33 | description: "Translated text." 34 | runs: 35 | using: "node24" 36 | main: "dist/index.js" 37 | -------------------------------------------------------------------------------- /src/__tests__/providers/MyMemoryProvider.spec.ts: -------------------------------------------------------------------------------- 1 | import MyMemoryProvider from '../../providers/MyMemoryProvider' 2 | import { config } from 'dotenv' 3 | import ProviderTester from './ProviderTester' 4 | 5 | config() 6 | 7 | type ProviderTesterWrapper = { 8 | providerTester: ProviderTester, 9 | type: string 10 | } 11 | 12 | describe('MyMemoryProvider', () => { 13 | const fixture: ProviderTesterWrapper[] = [{ 14 | providerTester: new ProviderTester(new MyMemoryProvider()), 15 | type: 'Free' 16 | }, { 17 | providerTester: new ProviderTester( 18 | new MyMemoryProvider(process.env.MYMEMORY_API_KEY) 19 | ), 20 | type: 'Registered' 21 | }] 22 | 23 | test.each(fixture)( 24 | '[$type] should get correct translation', 25 | ({ providerTester }: ProviderTesterWrapper) => providerTester.positive({ 26 | text: 'Night', lang: 'en|be', expected: 'Ноч' 27 | }) 28 | ) 29 | 30 | test.each(fixture)( 31 | '[$type] should fail because of invalid lang', 32 | async ({ providerTester }: ProviderTesterWrapper) => 33 | providerTester.negative() 34 | ) 35 | }) 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2025 Yevhen Fabizhevskyi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core' 2 | import extract from './extract' 3 | import type ProviderBase from './providers/ProviderBase' 4 | import { ProviderError } from './providers/ProviderBase' 5 | import ProviderFactory, { type ProviderType } from './providers/ProviderFactory' 6 | 7 | async function run() { 8 | try { 9 | const source: string = extract(core.getInput('source')) 10 | const providerFactory: ProviderFactory = new ProviderFactory() 11 | const provider: ProviderBase = providerFactory.getProvider( 12 | core.getInput('provider', { required: true, trimWhitespace: true }) as ProviderType, 13 | core.getInput('api_key', { required: false, trimWhitespace: true }), 14 | core.getInput('api_additional_parameter', { required: false, trimWhitespace: true }) 15 | ) 16 | let text: string 17 | try { 18 | text = (await provider.translate(source, core.getInput('lang')))[0] 19 | } catch (e) { 20 | if (e instanceof ProviderError) { 21 | text = source 22 | } else { 23 | throw e 24 | } 25 | } 26 | core.setOutput('text', text) 27 | } catch (e) { 28 | core.setFailed((e).message) 29 | } 30 | } 31 | 32 | run() 33 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "linter": { 3 | "enabled": true, 4 | "rules": { 5 | "recommended": true, 6 | "complexity": { 7 | "noAdjacentSpacesInRegex": "error", 8 | "noArguments": "error", 9 | "noExtraBooleanCast": "error", 10 | "noUselessCatch": "error" 11 | }, 12 | "correctness": { 13 | "noUndeclaredVariables": "error", 14 | "noUnusedLabels": "error", 15 | "noUnusedVariables": "error", 16 | "useJsxKeyInIterable": "error" 17 | }, 18 | "style": { 19 | "useBlockStatements": "error", 20 | "useConst": "error", 21 | "useSingleVarDeclarator": "error" 22 | }, 23 | "suspicious": { 24 | "noCatchAssign": "error", 25 | "noEmptyBlockStatements": "warn", 26 | "noPrototypeBuiltins": "error", 27 | "noVar": "error", 28 | "useIterableCallbackReturn": "off" 29 | } 30 | }, 31 | "includes": ["src/**", "tests/**"] 32 | }, 33 | "javascript": { 34 | "globals": ["Atomics", "SharedArrayBuffer", "jest", "afterEach", "beforeAll", "beforeEach", "expect", "test", "describe"] 35 | }, 36 | "overrides": [{ "includes": ["**/*.spec.ts", "**/*.spec.tsx"] }] 37 | } 38 | -------------------------------------------------------------------------------- /src/providers/ProviderFactory.ts: -------------------------------------------------------------------------------- 1 | import type ProviderBase from './ProviderBase' 2 | import FunTranslationsProvider from './FunTranslationsProvider' 3 | import LibreTranslateProvider from './LibreTranslateProvider' 4 | import LinguaToolsProvider from './LinguaToolsProvider' 5 | import MicrosoftProvider from './MicrosoftProvider' 6 | import MyMemoryProvider from './MyMemoryProvider' 7 | import DeeplProvider from './DeeplProvider' 8 | import GoogleProvider from './GoogleProvider' 9 | 10 | export type ProviderType = 11 | 'deepl' | 12 | 'google' | 13 | 'funtranslations' | 14 | 'linguatools' | 15 | 'microsoft' | 16 | 'mymemory' | 17 | 'libretranslate' 18 | 19 | export default class ProviderFactory { 20 | getProvider( 21 | type: ProviderType, apiKey: string, apiAdditionalParam: string 22 | ): ProviderBase { 23 | switch (type) { 24 | case 'deepl': 25 | return new DeeplProvider(apiKey) 26 | case 'google': 27 | return new GoogleProvider() 28 | case 'funtranslations': 29 | return new FunTranslationsProvider() 30 | case 'libretranslate': 31 | return new LibreTranslateProvider(apiKey) 32 | case 'linguatools': 33 | return new LinguaToolsProvider() 34 | case 'microsoft': 35 | return new MicrosoftProvider(apiKey, apiAdditionalParam) 36 | case 'mymemory': 37 | return new MyMemoryProvider(apiKey) 38 | default: 39 | throw new Error(`${type} is not supported`) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "translation-action", 3 | "version": "4.0.1", 4 | "description": "This GitHub action translates any text from any language to any language.", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "build": "esbuild src/index.ts --bundle --platform=node --target=node24 --outfile=dist/index.js", 8 | "lint": "biome lint --write src", 9 | "test": "jest --config=jest.config.json --json --outputFile=jest-report.json --coverage" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/fabasoad/translation-action.git" 14 | }, 15 | "keywords": [ 16 | "actions", 17 | "translation", 18 | "translator", 19 | "lang" 20 | ], 21 | "author": "Yevhen Fabizhevskyi", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/fabasoad/translation-action/issues" 25 | }, 26 | "homepage": "https://github.com/fabasoad/translation-action#readme", 27 | "dependencies": { 28 | "@actions/core": "~2.0.1", 29 | "cross-fetch": "~4.1.0", 30 | "deepl-node": "~1.24.0", 31 | "google-translate-api-x": "~10.7.2", 32 | "typed-rest-client": "~2.1.0" 33 | }, 34 | "devDependencies": { 35 | "@biomejs/biome": "~2.3.8", 36 | "@types/jest": "~30.0.0", 37 | "dotenv": "~17.2.3", 38 | "esbuild": "~0.27.1", 39 | "jest": "~30.2.0", 40 | "jest-circus": "~30.2.0", 41 | "ts-jest": "~29.4.6", 42 | "typescript": "~5.9.3" 43 | }, 44 | "packageManager": "yarn@4.12.0" 45 | } 46 | -------------------------------------------------------------------------------- /.github/workflows/test-source.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Test source 3 | 4 | on: # yamllint disable-line rule:truthy 5 | pull_request: 6 | paths: 7 | - .github/workflows/test-source.yml 8 | - dist/** 9 | - src/** 10 | - action.yml 11 | push: 12 | branches: 13 | - main 14 | 15 | defaults: 16 | run: 17 | shell: sh 18 | 19 | jobs: 20 | test-source: 21 | name: Source as ${{ matrix.source }} 22 | timeout-minutes: 5 23 | runs-on: ubuntu-latest 24 | env: 25 | TEXT: "Love" 26 | EXPECTED: "Любов" 27 | strategy: 28 | matrix: 29 | source: ["file", "text"] 30 | steps: 31 | - name: Checkout ${{ github.repository }} 32 | uses: actions/checkout@v5 33 | - name: Prepare source 34 | id: params 35 | run: | 36 | if [ "${{ matrix.source }}" = "file" ]; then 37 | source="source.txt" 38 | echo "${TEXT}" > "${source}" 39 | else 40 | source="${TEXT}" 41 | fi 42 | echo "source=${source}" >> "$GITHUB_OUTPUT" 43 | - name: Translate 44 | uses: ./ 45 | id: translate 46 | with: 47 | provider: "deepl" 48 | lang: "en-uk" 49 | source: "${{ steps.params.outputs.source }}" 50 | api_key: "${{ secrets.DEEPL_API_KEY }}" 51 | - name: Validate translated text 52 | run: | 53 | echo "'${TEXT}' has been translated to '${{ steps.translate.outputs.text }}'" 54 | [ "${{ steps.translate.outputs.text }}" = "${EXPECTED}" ] || exit 1; 55 | -------------------------------------------------------------------------------- /src/__tests__/providers/ProviderTester.ts: -------------------------------------------------------------------------------- 1 | import type ProviderBase from '../../providers/ProviderBase' 2 | import { ProviderError } from '../../providers/ProviderBase' 3 | 4 | type NegativeOptions = { 5 | lang: string 6 | text: string 7 | } 8 | 9 | type PositiveOptions = NegativeOptions & { 10 | expected: string 11 | } 12 | 13 | export default class ProviderTester { 14 | private readonly provider: ProviderBase 15 | 16 | constructor(provider: ProviderBase) { 17 | this.provider = provider 18 | } 19 | 20 | async positive({ text, lang, expected }: PositiveOptions = { 21 | text: 'Poem', 22 | lang: 'en-uk', 23 | expected: 'Вірш' 24 | }): Promise { 25 | try { 26 | const translations: string[] = await this.provider.translate(text, lang) 27 | expect(translations.length).toBeGreaterThan(0) 28 | expect(translations[0]).toEqual(expected) 29 | } catch (e: unknown) { 30 | let sc: number 31 | if (e instanceof ProviderError) { 32 | sc = (e).status 33 | } else { 34 | const { statusCode } = e as never 35 | sc = statusCode 36 | } 37 | if (sc !== 429) { 38 | throw e 39 | } 40 | } 41 | } 42 | 43 | async negative({ text, lang }: NegativeOptions = { 44 | text: 'Anything', 45 | lang: 'en-abc123' 46 | }): Promise { 47 | try { 48 | await this.provider.translate(text, lang) 49 | } catch (e) { 50 | expect(e).toBeTruthy() 51 | return 52 | } 53 | throw new Error('Request should fail due to unknown target language') 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/providers/ProviderBase.ts: -------------------------------------------------------------------------------- 1 | import { type IRestResponse, RestClient } from 'typed-rest-client/RestClient' 2 | import type { IHeaders } from 'typed-rest-client/Interfaces' 3 | 4 | export class ProviderError extends Error { 5 | private readonly _status: number 6 | constructor(status: number, message: string) { 7 | super(message) 8 | this._status = status 9 | } 10 | public get status(): number { 11 | return this._status 12 | } 13 | } 14 | 15 | export interface ApiProps { 16 | data?: object 17 | headers?: IHeaders 18 | method: string 19 | url: string 20 | } 21 | 22 | export default abstract class ProviderBase { 23 | private readonly baseUrl?: string 24 | private readonly client: RestClient 25 | 26 | protected constructor(baseUrl?: string) { 27 | this.baseUrl = baseUrl 28 | this.client = new RestClient(null, baseUrl) 29 | } 30 | 31 | // eslint-disable-next-line no-unused-vars 32 | abstract translate(text: string, lang: string): Promise 33 | protected async api(props: ApiProps): Promise { 34 | let response: IRestResponse 35 | if (props.method.toUpperCase() === 'GET') { 36 | response = await this.client.get( 37 | props.url, { additionalHeaders: props.headers }) 38 | } else { 39 | response = await this.client.create( 40 | props.url, props.data, { additionalHeaders: props.headers }) 41 | } 42 | if (response.statusCode < 200 || response.statusCode >= 400) { 43 | throw new ProviderError( 44 | response.statusCode, 45 | `[${response.statusCode}] Failed to call ${this.baseUrl}${props.url}`) 46 | } 47 | return response.result as T 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Pull request checklist 4 | 5 | Please check if your PR fulfills the following requirements: 6 | 7 | - [ ] I have read the [CONTRIBUTING](https://github.com/fabasoad/translation-action/blob/main/CONTRIBUTING.md) 8 | doc. 9 | - [ ] Tests for the changes have been added (for bug fixes / features). 10 | - [ ] Docs have been reviewed and added / updated if needed (for bug fixes / features). 11 | - [ ] Build (`yarn run build`) was run locally and any changes were pushed. 12 | - [ ] Tests (`yarn test`) has passed locally and any fixes were made for failures. 13 | 14 | ## Pull request type 15 | 16 | 17 | 18 | 20 | 21 | Please check the type of change your PR introduces: 22 | 23 | - [ ] Bugfix 24 | - [ ] Feature 25 | - [ ] Code style update (formatting, renaming) 26 | - [ ] Refactoring (no functional changes, no api changes) 27 | - [ ] Build related changes 28 | - [ ] Documentation content changes 29 | - [ ] Other (please describe): 30 | 31 | ## What is the current behavior 32 | 34 | 35 | ## What is the new behavior 36 | 37 | 38 | - 39 | - 40 | - 41 | 42 | ## Does this introduce a breaking change 43 | 44 | - [ ] Yes 45 | - [ ] No 46 | 47 | 49 | 50 | ## Other information 51 | 52 | 54 | 56 | 57 | --- 58 | 59 | Closes #{IssueNumber} 60 | -------------------------------------------------------------------------------- /.github/workflows/test-providers.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Test providers 3 | 4 | on: # yamllint disable-line rule:truthy 5 | pull_request: 6 | paths: 7 | - .github/workflows/test-providers.yml 8 | - dist/** 9 | - src/** 10 | - action.yml 11 | push: 12 | branches: 13 | - main 14 | workflow_dispatch: {} 15 | 16 | defaults: 17 | run: 18 | shell: sh 19 | 20 | jobs: 21 | test-providers: 22 | name: ${{ matrix.provider }} (${{ matrix.api_key == '' && 'Free' || 'Paid' }}) 23 | timeout-minutes: 5 24 | runs-on: ubuntu-latest 25 | strategy: 26 | fail-fast: false 27 | matrix: 28 | include: 29 | - provider: "deepl" 30 | lang: "en-uk" 31 | source: "Love" 32 | expected: "Любов" 33 | api_key: "DEEPL_API_KEY" # pragma: allowlist secret 34 | - provider: "google" 35 | lang: "en|zh-CN" 36 | source: "Love" 37 | expected: "爱" 38 | - provider: "linguatools" 39 | lang: "en-de" 40 | source: "Love" 41 | expected: "Love" 42 | - provider: "mymemory" 43 | lang: "en|sw" 44 | source: "Love" 45 | expected: "Upendo" 46 | - provider: "mymemory" 47 | lang: "en|pt" 48 | source: "Love" 49 | expected: "Amor" 50 | api_key: "MYMEMORY_API_KEY" # pragma: allowlist secret 51 | - provider: "funtranslations" 52 | lang: "klingon" 53 | source: "Love" 54 | expected: "Parmaq" 55 | steps: 56 | - name: Checkout ${{ github.repository }} 57 | uses: actions/checkout@v5 58 | - name: Translate (${{ matrix.provider }}, ${{ matrix.lang }}) 59 | uses: ./ 60 | id: result 61 | with: 62 | provider: ${{ matrix.provider }} 63 | lang: ${{ matrix.lang }} 64 | source: ${{ matrix.source }} 65 | api_key: "${{ matrix.api_key == '' && '' || secrets[matrix.api_key] }}" 66 | - name: Validate ${{ matrix.provider }} translation result 67 | run: | 68 | echo "'${{ matrix.source }}' has been translated to '${{ steps.result.outputs.text }}'" 69 | [ "${{ steps.result.outputs.text }}" = "${{ matrix.expected }}" ] || exit 1; 70 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing guidance 2 | 3 | We love your input! We want to make contributing to this project as easy and 4 | transparent as possible, whether it's: 5 | 6 | - Reporting a bug 7 | - Discussing the current state of the code 8 | - Submitting a fix 9 | - Proposing new features 10 | - Becoming a maintainer 11 | 12 | ## We develop with GitHub 13 | 14 | We use GitHub to host code, to track issues and feature requests, as well as 15 | accept pull requests. 16 | 17 | ## We use GitHub flow, so all code changes happen through pull requests 18 | 19 | Pull requests are the best way to propose changes to the codebase (we use 20 | [GitHub flow](https://guides.github.com/introduction/flow/index.html)). We 21 | actively welcome your pull requests: 22 | 23 | 1. Fork the repo and create your branch from `main`. 24 | 2. If you've added code that should be tested, add tests. 25 | 3. If you've changed APIs, update the documentation. 26 | 4. Ensure the test suite passes. 27 | 5. Make sure your code lints. 28 | 6. Issue that pull request! 29 | 30 | ## Any contributions you make will be under the MIT Software License 31 | 32 | In short, when you submit code changes, your submissions are understood to be 33 | under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers 34 | the project. Feel free to contact the maintainers if that's a concern. 35 | 36 | ## Report bugs using [GitHub Issues](https://github.com/fabasoad/translation-action/issues) 37 | 38 | We use GitHub issues to track public bugs. Report a bug by opening a new issue. 39 | It's that easy! 40 | 41 | ## Create issue using provided GitHub issue templates 42 | 43 | This repository has issue templates for bug report and feature request. Please 44 | use them to create an issue and fill all required fields. 45 | 46 | ## Use a consistent coding style 47 | 48 | Please follow all the rules from [this](https://google.github.io/styleguide/jsguide.html) 49 | great guide provided by Google for coding style except of following coding styles: 50 | 51 | - File names must be all lowercase and may include dashes (-). 52 | 53 | ## License 54 | 55 | By contributing, you agree that your contributions will be licensed under its 56 | MIT License. 57 | 58 | ## References 59 | 60 | This document was adapted from the open-source contribution guidelines provided 61 | by [briandk](https://gist.github.com/briandk/3d2e8b3ec8daf5a27a62). 62 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | default_install_hook_types: ["pre-commit", "pre-push"] 3 | default_stages: ["pre-commit", "pre-push"] 4 | exclude: ^(dist/.*\.js|\.yarn/.*)$ 5 | minimum_pre_commit_version: 4.4.0 6 | repos: 7 | - repo: local 8 | hooks: 9 | - id: build 10 | name: Build 11 | entry: make build 12 | language: unsupported 13 | pass_filenames: false 14 | verbose: true 15 | stages: ["pre-push"] 16 | - id: lint 17 | name: Lint 18 | entry: make lint 19 | language: unsupported 20 | pass_filenames: false 21 | verbose: false 22 | stages: ["pre-push"] 23 | - id: test 24 | name: Unit tests 25 | entry: make test 26 | language: unsupported 27 | pass_filenames: false 28 | verbose: true 29 | stages: ["pre-push"] 30 | # Security 31 | - id: audit 32 | name: Yarn audit 33 | entry: make audit 34 | language: unsupported 35 | pass_filenames: false 36 | verbose: false 37 | stages: ["pre-push"] 38 | - repo: https://github.com/Yelp/detect-secrets 39 | rev: v1.5.0 40 | hooks: 41 | - id: detect-secrets 42 | - repo: https://github.com/gitleaks/gitleaks 43 | rev: v8.30.0 44 | hooks: 45 | - id: gitleaks 46 | - repo: https://github.com/fabasoad/pre-commit-snyk 47 | rev: v1.0.2 48 | hooks: 49 | - id: snyk-test 50 | args: 51 | - --snyk-args=--all-projects --severity-threshold=low 52 | - --hook-args=--log-level debug 53 | stages: ["pre-push"] 54 | - repo: https://github.com/fabasoad/pre-commit-grype 55 | rev: v0.6.3 56 | hooks: 57 | - id: grype-dir 58 | args: 59 | - --grype-args=--by-cve --fail-on=low --exclude=**/node_modules 60 | - --hook-args=--log-level debug 61 | stages: ["pre-push"] 62 | - repo: https://github.com/google/osv-scanner 63 | rev: v2.3.1 64 | hooks: 65 | - id: osv-scanner 66 | args: 67 | - --lockfile=yarn.lock 68 | verbose: true 69 | stages: ["pre-push"] 70 | # Markdown 71 | - repo: https://github.com/igorshubovych/markdownlint-cli 72 | rev: v0.47.0 73 | hooks: 74 | - id: markdownlint-fix 75 | stages: ["pre-commit"] 76 | # Yaml 77 | - repo: https://github.com/adrienverge/yamllint 78 | rev: v1.37.1 79 | hooks: 80 | - id: yamllint 81 | stages: ["pre-push"] 82 | # GitHub Actions 83 | - repo: https://github.com/rhysd/actionlint 84 | rev: v1.7.9 85 | hooks: 86 | - id: actionlint 87 | args: ["-pyflakes="] 88 | stages: ["pre-commit"] 89 | # Other 90 | - repo: https://github.com/pre-commit/pre-commit-hooks 91 | rev: v6.0.0 92 | hooks: 93 | - id: check-merge-conflict 94 | - id: check-json 95 | stages: ["pre-push"] 96 | - id: check-toml 97 | stages: ["pre-push"] 98 | - id: detect-private-key 99 | - id: end-of-file-fixer 100 | - id: mixed-line-ending 101 | args: ["--fix=lf"] 102 | - id: no-commit-to-branch 103 | stages: ["pre-commit"] 104 | - id: trailing-whitespace 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Translation action 2 | 3 | [![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua) 4 | ![Releases](https://img.shields.io/github/v/release/fabasoad/translation-action?include_prereleases) 5 | ![unit-tests](https://github.com/fabasoad/translation-action/actions/workflows/unit-tests.yml/badge.svg) 6 | ![test-providers](https://github.com/fabasoad/translation-action/actions/workflows/test-providers.yml/badge.svg) 7 | ![test-source](https://github.com/fabasoad/translation-action/actions/workflows/test-source.yml/badge.svg) 8 | ![security](https://github.com/fabasoad/translation-action/actions/workflows/security.yml/badge.svg) 9 | ![linting](https://github.com/fabasoad/translation-action/actions/workflows/linting.yml/badge.svg) 10 | [![codecov](https://codecov.io/gh/fabasoad/translation-action/branch/main/graph/badge.svg?token=X0W90WW07U)](https://codecov.io/gh/fabasoad/translation-action) 11 | 12 | This action translates any text to any language supported by chosen provider. 13 | There is a list of providers that can be used for text translation. Please find 14 | more details for each provider below. 15 | 16 | ## Contents 17 | 18 | 19 | * [Translation action](#translation-action) 20 | * [Contents](#contents) 21 | * [Inputs](#inputs) 22 | * [Outputs](#outputs) 23 | * [Providers](#providers) 24 | * [DeepL](#deepl) 25 | * [Google](#google) 26 | * [LibreTranslate](#libretranslate) 27 | * [Linguatools](#linguatools) 28 | * [Microsoft](#microsoft) 29 | * [MyMemory](#mymemory) 30 | * [FunTranslations](#funtranslations) 31 | * [Contributions](#contributions) 32 | 33 | 34 | ## Inputs 35 | 36 | | Name | Required | Description | Default | Possible values | 37 | |--------------------------|----------|--------------------------------------------------------------------------------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 38 | | source | Yes | Can be text or path to the file for translation | | _<Path>_,_<String>_ | 39 | | provider | Yes | Provider identifier | | [deepl](#deepl), [google](#google), [libretranslate](#libretranslate), [linguatools](#linguatools), [microsoft](#microsoft), [mymemory](#mymemory), [funtranslations](#funtranslations) | 40 | | api_key | No | API key that should be used for chosen [provider](#providers) | `""` | _<String>_ | 41 | | api_additional_parameter | No | Additional parameter for the API. eg the region for Microsoft: `canadacentral` | `""` | _<String>_ | 42 | | lang | Yes | The translation direction. Should be one of the option proposed by chosen [provider](#providers) | | _<String>_ | 43 | 44 | ## Outputs 45 | 46 | | Name | Required | Description | 47 | |------|----------|-----------------| 48 | | text | Yes | Translated text | 49 | 50 | ## Providers 51 | 52 | ### DeepL 53 | 54 | * Identifier is `deepl`. 55 | * See [supported translation directions](https://www.deepl.com/docs-api/general/get-languages/) 56 | for more details. 57 | * Be aware that source and target languages should be separated by `-` (hyphen) 58 | character while using them in `lang` input. For example, `en-uk` should be used 59 | in case you want to translate text from English into Ukrainian. See example 60 | below for more details. 61 | * How to get API key: 62 | * Sign up to [DeepL](https://www.deepl.com) (free plan is fine). 63 | * Go to `Account -> Account -> Authentication Key for DeepL API` section 64 | 65 | Example of translating "Love" word from English into Ukrainian: 66 | 67 | ```yaml 68 | jobs: 69 | deepl: 70 | name: DeepL 71 | runs-on: ubuntu-latest 72 | steps: 73 | - uses: actions/checkout@v5 74 | - uses: fabasoad/translation-action@v4 75 | id: deepl-step 76 | with: 77 | provider: deepl 78 | lang: en-uk 79 | source: Love 80 | api_key: ${{ secrets.DEEPL_API_KEY }} 81 | - name: Print the result 82 | run: echo "Translation is '${{ steps.deepl-step.outputs.text }}'" 83 | shell: sh 84 | ``` 85 | 86 | Output is the following: 87 | 88 | ```text 89 | > echo "Translation is 'Любов'" 90 | Translation is 'Любов' 91 | ``` 92 | 93 | ### Google 94 | 95 | * Identifier is `google`. 96 | * See [supported translation directions](https://github.com/AidanWelch/google-translate-api/blob/v10.7.2/index.d.ts#L123) 97 | for more details. 98 | * Be aware that source and target languages should be separated by `|` (pipe) 99 | character while using them in `lang` input. For example, `ms|en` should be used 100 | in case you want to translate text from Malay into English. See example below 101 | for more details. 102 | 103 | Example of translating "Victory" word from Malay into English: 104 | 105 | ```yaml 106 | jobs: 107 | google: 108 | name: Google 109 | runs-on: ubuntu-latest 110 | steps: 111 | - uses: actions/checkout@v5 112 | - uses: fabasoad/translation-action@v4 113 | id: google-step 114 | with: 115 | provider: google 116 | lang: ms|en 117 | source: Kemenangan 118 | - name: Print the result 119 | run: echo "Translation is '${{ steps.google-step.outputs.text }}'" 120 | shell: sh 121 | ``` 122 | 123 | Output is the following: 124 | 125 | ```text 126 | > echo "Translation is 'Victory'" 127 | Translation is 'Victory' 128 | ``` 129 | 130 | ### LibreTranslate 131 | 132 | * Identifier is `libretranslate`. 133 | * See [supported translation directions](https://libretranslate.com/languages) 134 | for more details. 135 | * Be aware that source and target languages should be separated by `-` (hyphen) 136 | character while using them in `lang` input. For example, `en-es` should be used 137 | in case you want to translate text from English into Spanish. See example 138 | below for more details. 139 | * How to get API key: 140 | * Sign up to [LibreTranslate](https://portal.libretranslate.com/). 141 | * Go to `Home -> Get API Key` section 142 | 143 | Example of translating "Victory" word from English into Ukrainian: 144 | 145 | ```yaml 146 | jobs: 147 | libretranslate: 148 | name: LibreTranslate 149 | runs-on: ubuntu-latest 150 | steps: 151 | - uses: actions/checkout@v5 152 | - uses: fabasoad/translation-action@v4 153 | id: libretranslate-step 154 | with: 155 | provider: libretranslate 156 | lang: en-uk 157 | source: Victory 158 | api_key: ${{ secrets.LIBRETRANSLATE_API_KEY }} 159 | - name: Print the result 160 | run: echo "Translation is '${{ steps.libretranslate-step.outputs.text }}'" 161 | shell: sh 162 | ``` 163 | 164 | Output is the following: 165 | 166 | ```text 167 | > echo "Translation is 'Перемога'" 168 | Translation is 'Перемога' 169 | ``` 170 | 171 | ### Linguatools 172 | 173 | * Identifier is `linguatools`. API Key is not needed for this provider. 174 | * Supported translation directions: 175 | 176 | ```text 177 | "de-en","de-es","de-nl","de-pl","de-it","de-cs","en-de","es-de","nl-de","pl-de","it-de","cs-de" 178 | ``` 179 | 180 | ### Microsoft 181 | 182 | * Identifier is `microsoft`. 183 | * Supported translation directions: 184 | 185 | ```text 186 | "af","ar","bg","bn","bs","ca","cs","cy","da","de","el","en","es","et","fa","fi","fr","he","hi","hr","ht","hu","id","is","it","ja","ko","lt","lv","ms","mt","mww","nb","nl","pl","pt","ro","ru","sk","sl","sr-Latn","sv","sw","ta","th","tlh-Latn","tr","uk","ur","vi","zh-Hans" 187 | ``` 188 | 189 | * How to get API key: 190 | 191 | Please follow the steps described in [this](https://docs.microsoft.com/en-us/azure/cognitive-services/translator/translator-text-how-to-signup) 192 | article. 193 | 194 | You will also need to provide the region of the key using the 195 | `api_additional_parameter`, e.g.: 196 | 197 | ```YAML 198 | with: 199 | api_additional_parameter: canadacentral 200 | ``` 201 | 202 | ### MyMemory 203 | 204 | * Identifier is `mymemory`. 205 | * [Supported translation directions](https://mymemory.translated.net/doc/spec.php): 206 | 207 | Language direction should be separated by `|` character. For example, `en|it` 208 | (from English to Italian). 209 | 210 | * How to get API key: 211 | 212 | API Key is _optional_. Visit [Usage Limit Page](https://mymemory.translated.net/doc/usagelimits.php) 213 | to see the usage limit for free accounts. In case you want to use your API KEY, 214 | you should go to [Registration Page](https://www.translated.net/top/) and 215 | register a new account. Then go to [API Key Generator Page](https://mymemory.translated.net/doc/keygen.php) 216 | and generate a new key. 217 | 218 | ### FunTranslations 219 | 220 | * Identifier is `funtranslations`. 221 | * [Supported translation directions](https://funtranslations.com/api/): 222 | 223 | > `from` direction is English only at this moment. 224 | 225 | Example: 226 | 227 | ```yaml 228 | - uses: fabasoad/translation-action@v4 229 | with: 230 | provider: funtranslations 231 | lang: 'klingon' 232 | source: 'Who are you' 233 | ``` 234 | 235 | ## Contributions 236 | 237 | ![Alt](https://repobeats.axiom.co/api/embed/666b4ed5b82273f6ecae26db931e5c9dd71b65b1.svg "Repobeats analytics image") 238 | -------------------------------------------------------------------------------- /.yarn/plugins/@yarnpkg/plugin-outdated.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | //prettier-ignore 3 | module.exports = { 4 | name: "@yarnpkg/plugin-outdated", 5 | factory: function (require) { 6 | var plugin=(()=>{var Cr=Object.create,ge=Object.defineProperty,Er=Object.defineProperties,_r=Object.getOwnPropertyDescriptor,xr=Object.getOwnPropertyDescriptors,br=Object.getOwnPropertyNames,et=Object.getOwnPropertySymbols,Sr=Object.getPrototypeOf,tt=Object.prototype.hasOwnProperty,vr=Object.prototype.propertyIsEnumerable;var rt=(e,t,r)=>t in e?ge(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,k=(e,t)=>{for(var r in t||(t={}))tt.call(t,r)&&rt(e,r,t[r]);if(et)for(var r of et(t))vr.call(t,r)&&rt(e,r,t[r]);return e},q=(e,t)=>Er(e,xr(t)),Hr=e=>ge(e,"__esModule",{value:!0});var W=e=>{if(typeof require!="undefined")return require(e);throw new Error('Dynamic require of "'+e+'" is not supported')};var U=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),wr=(e,t)=>{for(var r in t)ge(e,r,{get:t[r],enumerable:!0})},Tr=(e,t,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of br(t))!tt.call(e,n)&&n!=="default"&&ge(e,n,{get:()=>t[n],enumerable:!(r=_r(t,n))||r.enumerable});return e},re=e=>Tr(Hr(ge(e!=null?Cr(Sr(e)):{},"default",e&&e.__esModule&&"default"in e?{get:()=>e.default,enumerable:!0}:{value:e,enumerable:!0})),e);var ve=U(V=>{"use strict";V.isInteger=e=>typeof e=="number"?Number.isInteger(e):typeof e=="string"&&e.trim()!==""?Number.isInteger(Number(e)):!1;V.find=(e,t)=>e.nodes.find(r=>r.type===t);V.exceedsLimit=(e,t,r=1,n)=>n===!1||!V.isInteger(e)||!V.isInteger(t)?!1:(Number(t)-Number(e))/Number(r)>=n;V.escapeNode=(e,t=0,r)=>{let n=e.nodes[t];!n||(r&&n.type===r||n.type==="open"||n.type==="close")&&n.escaped!==!0&&(n.value="\\"+n.value,n.escaped=!0)};V.encloseBrace=e=>e.type!=="brace"?!1:e.commas>>0+e.ranges>>0==0?(e.invalid=!0,!0):!1;V.isInvalidBrace=e=>e.type!=="brace"?!1:e.invalid===!0||e.dollar?!0:e.commas>>0+e.ranges>>0==0||e.open!==!0||e.close!==!0?(e.invalid=!0,!0):!1;V.isOpenOrClose=e=>e.type==="open"||e.type==="close"?!0:e.open===!0||e.close===!0;V.reduce=e=>e.reduce((t,r)=>(r.type==="text"&&t.push(r.value),r.type==="range"&&(r.type="text"),t),[]);V.flatten=(...e)=>{let t=[],r=n=>{for(let s=0;s{"use strict";var nt=ve();st.exports=(e,t={})=>{let r=(n,s={})=>{let a=t.escapeInvalid&&nt.isInvalidBrace(s),i=n.invalid===!0&&t.escapeInvalid===!0,o="";if(n.value)return(a||i)&&nt.isOpenOrClose(n)?"\\"+n.value:n.value;if(n.value)return n.value;if(n.nodes)for(let f of n.nodes)o+=r(f);return o};return r(e)}});var it=U((es,at)=>{"use strict";at.exports=function(e){return typeof e=="number"?e-e==0:typeof e=="string"&&e.trim()!==""?Number.isFinite?Number.isFinite(+e):isFinite(+e):!1}});var gt=U((ts,dt)=>{"use strict";var ot=it(),ce=(e,t,r)=>{if(ot(e)===!1)throw new TypeError("toRegexRange: expected the first argument to be a number");if(t===void 0||e===t)return String(e);if(ot(t)===!1)throw new TypeError("toRegexRange: expected the second argument to be a number.");let n=k({relaxZeros:!0},r);typeof n.strictZeros=="boolean"&&(n.relaxZeros=n.strictZeros===!1);let s=String(n.relaxZeros),a=String(n.shorthand),i=String(n.capture),o=String(n.wrap),f=e+":"+t+"="+s+a+i+o;if(ce.cache.hasOwnProperty(f))return ce.cache[f].result;let y=Math.min(e,t),h=Math.max(e,t);if(Math.abs(y-h)===1){let A=e+"|"+t;return n.capture?`(${A})`:n.wrap===!1?A:`(?:${A})`}let R=ht(e)||ht(t),p={min:e,max:t,a:y,b:h},H=[],m=[];if(R&&(p.isPadded=R,p.maxLen=String(p.max).length),y<0){let A=h<0?Math.abs(h):1;m=ut(A,Math.abs(y),p,n),y=p.a=0}return h>=0&&(H=ut(y,h,p,n)),p.negatives=m,p.positives=H,p.result=$r(m,H,n),n.capture===!0?p.result=`(${p.result})`:n.wrap!==!1&&H.length+m.length>1&&(p.result=`(?:${p.result})`),ce.cache[f]=p,p.result};function $r(e,t,r){let n=Ne(e,t,"-",!1,r)||[],s=Ne(t,e,"",!1,r)||[],a=Ne(e,t,"-?",!0,r)||[];return n.concat(a).concat(s).join("|")}function Lr(e,t){let r=1,n=1,s=lt(e,r),a=new Set([t]);for(;e<=s&&s<=t;)a.add(s),r+=1,s=lt(e,r);for(s=pt(t+1,n)-1;e1&&o.count.pop(),o.count.push(h.count[0]),o.string=o.pattern+ft(o.count),i=y+1;continue}r.isPadded&&(R=Pr(y,r,n)),h.string=R+h.pattern+ft(h.count),a.push(h),i=y+1,o=h}return a}function Ne(e,t,r,n,s){let a=[];for(let i of e){let{string:o}=i;!n&&!ct(t,"string",o)&&a.push(r+o),n&&ct(t,"string",o)&&a.push(r+o)}return a}function kr(e,t){let r=[];for(let n=0;nt?1:t>e?-1:0}function ct(e,t,r){return e.some(n=>n[t]===r)}function lt(e,t){return Number(String(e).slice(0,-t)+"9".repeat(t))}function pt(e,t){return e-e%Math.pow(10,t)}function ft(e){let[t=0,r=""]=e;return r||t>1?`{${t+(r?","+r:"")}}`:""}function Ir(e,t,r){return`[${e}${t-e==1?"":"-"}${t}]`}function ht(e){return/^-?(0+)\d/.test(e)}function Pr(e,t,r){if(!t.isPadded)return e;let n=Math.abs(t.maxLen-String(e).length),s=r.relaxZeros!==!1;switch(n){case 0:return"";case 1:return s?"0?":"0";case 2:return s?"0{0,2}":"00";default:return s?`0{0,${n}}`:`0{${n}}`}}ce.cache={};ce.clearCache=()=>ce.cache={};dt.exports=ce});var De=U((rs,xt)=>{"use strict";var Dr=W("util"),yt=gt(),Rt=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),Mr=e=>t=>e===!0?Number(t):String(t),Ie=e=>typeof e=="number"||typeof e=="string"&&e!=="",ye=e=>Number.isInteger(+e),Pe=e=>{let t=`${e}`,r=-1;if(t[0]==="-"&&(t=t.slice(1)),t==="0")return!1;for(;t[++r]==="0";);return r>0},Br=(e,t,r)=>typeof e=="string"||typeof t=="string"?!0:r.stringify===!0,Ur=(e,t,r)=>{if(t>0){let n=e[0]==="-"?"-":"";n&&(e=e.slice(1)),e=n+e.padStart(n?t-1:t,"0")}return r===!1?String(e):e},At=(e,t)=>{let r=e[0]==="-"?"-":"";for(r&&(e=e.slice(1),t--);e.length{e.negatives.sort((i,o)=>io?1:0),e.positives.sort((i,o)=>io?1:0);let r=t.capture?"":"?:",n="",s="",a;return e.positives.length&&(n=e.positives.join("|")),e.negatives.length&&(s=`-(${r}${e.negatives.join("|")})`),n&&s?a=`${n}|${s}`:a=n||s,t.wrap?`(${r}${a})`:a},mt=(e,t,r,n)=>{if(r)return yt(e,t,k({wrap:!1},n));let s=String.fromCharCode(e);if(e===t)return s;let a=String.fromCharCode(t);return`[${s}-${a}]`},Ct=(e,t,r)=>{if(Array.isArray(e)){let n=r.wrap===!0,s=r.capture?"":"?:";return n?`(${s}${e.join("|")})`:e.join("|")}return yt(e,t,r)},Et=(...e)=>new RangeError("Invalid range arguments: "+Dr.inspect(...e)),_t=(e,t,r)=>{if(r.strictRanges===!0)throw Et([e,t]);return[]},Fr=(e,t)=>{if(t.strictRanges===!0)throw new TypeError(`Expected step "${e}" to be a number`);return[]},jr=(e,t,r=1,n={})=>{let s=Number(e),a=Number(t);if(!Number.isInteger(s)||!Number.isInteger(a)){if(n.strictRanges===!0)throw Et([e,t]);return[]}s===0&&(s=0),a===0&&(a=0);let i=s>a,o=String(e),f=String(t),y=String(r);r=Math.max(Math.abs(r),1);let h=Pe(o)||Pe(f)||Pe(y),R=h?Math.max(o.length,f.length,y.length):0,p=h===!1&&Br(e,t,n)===!1,H=n.transform||Mr(p);if(n.toRegex&&r===1)return mt(At(e,R),At(t,R),!0,n);let m={negatives:[],positives:[]},A=O=>m[O<0?"negatives":"positives"].push(Math.abs(O)),E=[],b=0;for(;i?s>=a:s<=a;)n.toRegex===!0&&r>1?A(s):E.push(Ur(H(s,b),R,p)),s=i?s-r:s+r,b++;return n.toRegex===!0?r>1?Gr(m,n):Ct(E,null,k({wrap:!1},n)):E},Kr=(e,t,r=1,n={})=>{if(!ye(e)&&e.length>1||!ye(t)&&t.length>1)return _t(e,t,n);let s=n.transform||(p=>String.fromCharCode(p)),a=`${e}`.charCodeAt(0),i=`${t}`.charCodeAt(0),o=a>i,f=Math.min(a,i),y=Math.max(a,i);if(n.toRegex&&r===1)return mt(f,y,!1,n);let h=[],R=0;for(;o?a>=i:a<=i;)h.push(s(a,R)),a=o?a-r:a+r,R++;return n.toRegex===!0?Ct(h,null,{wrap:!1,options:n}):h},we=(e,t,r,n={})=>{if(t==null&&Ie(e))return[e];if(!Ie(e)||!Ie(t))return _t(e,t,n);if(typeof r=="function")return we(e,t,1,{transform:r});if(Rt(r))return we(e,t,0,r);let s=k({},n);return s.capture===!0&&(s.wrap=!0),r=r||s.step||1,ye(r)?ye(e)&&ye(t)?jr(e,t,r,s):Kr(e,t,Math.max(Math.abs(r),1),s):r!=null&&!Rt(r)?Fr(r,s):we(e,t,1,r)};xt.exports=we});var vt=U((ns,St)=>{"use strict";var qr=De(),bt=ve(),Wr=(e,t={})=>{let r=(n,s={})=>{let a=bt.isInvalidBrace(s),i=n.invalid===!0&&t.escapeInvalid===!0,o=a===!0||i===!0,f=t.escapeInvalid===!0?"\\":"",y="";if(n.isOpen===!0||n.isClose===!0)return f+n.value;if(n.type==="open")return o?f+n.value:"(";if(n.type==="close")return o?f+n.value:")";if(n.type==="comma")return n.prev.type==="comma"?"":o?n.value:"|";if(n.value)return n.value;if(n.nodes&&n.ranges>0){let h=bt.reduce(n.nodes),R=qr(...h,q(k({},t),{wrap:!1,toRegex:!0}));if(R.length!==0)return h.length>1&&R.length>1?`(${R})`:R}if(n.nodes)for(let h of n.nodes)y+=r(h,n);return y};return r(e)};St.exports=Wr});var Tt=U((ss,wt)=>{"use strict";var Qr=De(),Ht=He(),pe=ve(),le=(e="",t="",r=!1)=>{let n=[];if(e=[].concat(e),t=[].concat(t),!t.length)return e;if(!e.length)return r?pe.flatten(t).map(s=>`{${s}}`):t;for(let s of e)if(Array.isArray(s))for(let a of s)n.push(le(a,t,r));else for(let a of t)r===!0&&typeof a=="string"&&(a=`{${a}}`),n.push(Array.isArray(a)?le(s,a,r):s+a);return pe.flatten(n)},Xr=(e,t={})=>{let r=t.rangeLimit===void 0?1e3:t.rangeLimit,n=(s,a={})=>{s.queue=[];let i=a,o=a.queue;for(;i.type!=="brace"&&i.type!=="root"&&i.parent;)i=i.parent,o=i.queue;if(s.invalid||s.dollar){o.push(le(o.pop(),Ht(s,t)));return}if(s.type==="brace"&&s.invalid!==!0&&s.nodes.length===2){o.push(le(o.pop(),["{}"]));return}if(s.nodes&&s.ranges>0){let R=pe.reduce(s.nodes);if(pe.exceedsLimit(...R,t.step,r))throw new RangeError("expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.");let p=Qr(...R,t);p.length===0&&(p=Ht(s,t)),o.push(le(o.pop(),p)),s.nodes=[];return}let f=pe.encloseBrace(s),y=s.queue,h=s;for(;h.type!=="brace"&&h.type!=="root"&&h.parent;)h=h.parent,y=h.queue;for(let R=0;R{"use strict";$t.exports={MAX_LENGTH:1024*64,CHAR_0:"0",CHAR_9:"9",CHAR_UPPERCASE_A:"A",CHAR_LOWERCASE_A:"a",CHAR_UPPERCASE_Z:"Z",CHAR_LOWERCASE_Z:"z",CHAR_LEFT_PARENTHESES:"(",CHAR_RIGHT_PARENTHESES:")",CHAR_ASTERISK:"*",CHAR_AMPERSAND:"&",CHAR_AT:"@",CHAR_BACKSLASH:"\\",CHAR_BACKTICK:"`",CHAR_CARRIAGE_RETURN:"\r",CHAR_CIRCUMFLEX_ACCENT:"^",CHAR_COLON:":",CHAR_COMMA:",",CHAR_DOLLAR:"$",CHAR_DOT:".",CHAR_DOUBLE_QUOTE:'"',CHAR_EQUAL:"=",CHAR_EXCLAMATION_MARK:"!",CHAR_FORM_FEED:"\f",CHAR_FORWARD_SLASH:"/",CHAR_HASH:"#",CHAR_HYPHEN_MINUS:"-",CHAR_LEFT_ANGLE_BRACKET:"<",CHAR_LEFT_CURLY_BRACE:"{",CHAR_LEFT_SQUARE_BRACKET:"[",CHAR_LINE_FEED:` 7 | `,CHAR_NO_BREAK_SPACE:"\xA0",CHAR_PERCENT:"%",CHAR_PLUS:"+",CHAR_QUESTION_MARK:"?",CHAR_RIGHT_ANGLE_BRACKET:">",CHAR_RIGHT_CURLY_BRACE:"}",CHAR_RIGHT_SQUARE_BRACKET:"]",CHAR_SEMICOLON:";",CHAR_SINGLE_QUOTE:"'",CHAR_SPACE:" ",CHAR_TAB:" ",CHAR_UNDERSCORE:"_",CHAR_VERTICAL_LINE:"|",CHAR_ZERO_WIDTH_NOBREAK_SPACE:"\uFEFF"}});var Pt=U((is,It)=>{"use strict";var zr=He(),{MAX_LENGTH:Ot,CHAR_BACKSLASH:Me,CHAR_BACKTICK:Zr,CHAR_COMMA:Vr,CHAR_DOT:Yr,CHAR_LEFT_PARENTHESES:Jr,CHAR_RIGHT_PARENTHESES:en,CHAR_LEFT_CURLY_BRACE:tn,CHAR_RIGHT_CURLY_BRACE:rn,CHAR_LEFT_SQUARE_BRACKET:kt,CHAR_RIGHT_SQUARE_BRACKET:Nt,CHAR_DOUBLE_QUOTE:nn,CHAR_SINGLE_QUOTE:sn,CHAR_NO_BREAK_SPACE:an,CHAR_ZERO_WIDTH_NOBREAK_SPACE:on}=Lt(),un=(e,t={})=>{if(typeof e!="string")throw new TypeError("Expected a string");let r=t||{},n=typeof r.maxLength=="number"?Math.min(Ot,r.maxLength):Ot;if(e.length>n)throw new SyntaxError(`Input length (${e.length}), exceeds max characters (${n})`);let s={type:"root",input:e,nodes:[]},a=[s],i=s,o=s,f=0,y=e.length,h=0,R=0,p,H={},m=()=>e[h++],A=E=>{if(E.type==="text"&&o.type==="dot"&&(o.type="text"),o&&o.type==="text"&&E.type==="text"){o.value+=E.value;return}return i.nodes.push(E),E.parent=i,E.prev=o,o=E,E};for(A({type:"bos"});h0){if(i.ranges>0){i.ranges=0;let E=i.nodes.shift();i.nodes=[E,{type:"text",value:zr(i)}]}A({type:"comma",value:p}),i.commas++;continue}if(p===Yr&&R>0&&i.commas===0){let E=i.nodes;if(R===0||E.length===0){A({type:"text",value:p});continue}if(o.type==="dot"){if(i.range=[],o.value+=p,o.type="range",i.nodes.length!==3&&i.nodes.length!==5){i.invalid=!0,i.ranges=0,o.type="text";continue}i.ranges++,i.args=[];continue}if(o.type==="range"){E.pop();let b=E[E.length-1];b.value+=o.value+p,o=b,i.ranges--;continue}A({type:"dot",value:p});continue}A({type:"text",value:p})}do if(i=a.pop(),i.type!=="root"){i.nodes.forEach(O=>{O.nodes||(O.type==="open"&&(O.isOpen=!0),O.type==="close"&&(O.isClose=!0),O.nodes||(O.type="text"),O.invalid=!0)});let E=a[a.length-1],b=E.nodes.indexOf(i);E.nodes.splice(b,1,...i.nodes)}while(a.length>0);return A({type:"eos"}),s};It.exports=un});var Bt=U((os,Mt)=>{"use strict";var Dt=He(),cn=vt(),ln=Tt(),pn=Pt(),z=(e,t={})=>{let r=[];if(Array.isArray(e))for(let n of e){let s=z.create(n,t);Array.isArray(s)?r.push(...s):r.push(s)}else r=[].concat(z.create(e,t));return t&&t.expand===!0&&t.nodupes===!0&&(r=[...new Set(r)]),r};z.parse=(e,t={})=>pn(e,t);z.stringify=(e,t={})=>typeof e=="string"?Dt(z.parse(e,t),t):Dt(e,t);z.compile=(e,t={})=>(typeof e=="string"&&(e=z.parse(e,t)),cn(e,t));z.expand=(e,t={})=>{typeof e=="string"&&(e=z.parse(e,t));let r=ln(e,t);return t.noempty===!0&&(r=r.filter(Boolean)),t.nodupes===!0&&(r=[...new Set(r)]),r};z.create=(e,t={})=>e===""||e.length<3?[e]:t.expand!==!0?z.compile(e,t):z.expand(e,t);Mt.exports=z});var Re=U((us,Kt)=>{"use strict";var fn=W("path"),ne="\\\\/",Ut=`[^${ne}]`,ae="\\.",hn="\\+",dn="\\?",Te="\\/",gn="(?=.)",Gt="[^/]",Be=`(?:${Te}|$)`,Ft=`(?:^|${Te})`,Ue=`${ae}{1,2}${Be}`,yn=`(?!${ae})`,Rn=`(?!${Ft}${Ue})`,An=`(?!${ae}{0,1}${Be})`,mn=`(?!${Ue})`,Cn=`[^.${Te}]`,En=`${Gt}*?`,jt={DOT_LITERAL:ae,PLUS_LITERAL:hn,QMARK_LITERAL:dn,SLASH_LITERAL:Te,ONE_CHAR:gn,QMARK:Gt,END_ANCHOR:Be,DOTS_SLASH:Ue,NO_DOT:yn,NO_DOTS:Rn,NO_DOT_SLASH:An,NO_DOTS_SLASH:mn,QMARK_NO_DOT:Cn,STAR:En,START_ANCHOR:Ft},_n=q(k({},jt),{SLASH_LITERAL:`[${ne}]`,QMARK:Ut,STAR:`${Ut}*?`,DOTS_SLASH:`${ae}{1,2}(?:[${ne}]|$)`,NO_DOT:`(?!${ae})`,NO_DOTS:`(?!(?:^|[${ne}])${ae}{1,2}(?:[${ne}]|$))`,NO_DOT_SLASH:`(?!${ae}{0,1}(?:[${ne}]|$))`,NO_DOTS_SLASH:`(?!${ae}{1,2}(?:[${ne}]|$))`,QMARK_NO_DOT:`[^.${ne}]`,START_ANCHOR:`(?:^|[${ne}])`,END_ANCHOR:`(?:[${ne}]|$)`}),xn={alnum:"a-zA-Z0-9",alpha:"a-zA-Z",ascii:"\\x00-\\x7F",blank:" \\t",cntrl:"\\x00-\\x1F\\x7F",digit:"0-9",graph:"\\x21-\\x7E",lower:"a-z",print:"\\x20-\\x7E ",punct:"\\-!\"#$%&'()\\*+,./:;<=>?@[\\]^_`{|}~",space:" \\t\\r\\n\\v\\f",upper:"A-Z",word:"A-Za-z0-9_",xdigit:"A-Fa-f0-9"};Kt.exports={MAX_LENGTH:1024*64,POSIX_REGEX_SOURCE:xn,REGEX_BACKSLASH:/\\(?![*+?^${}(|)[\]])/g,REGEX_NON_SPECIAL_CHARS:/^[^@![\].,$*+?^{}()|\\/]+/,REGEX_SPECIAL_CHARS:/[-*+?.^${}(|)[\]]/,REGEX_SPECIAL_CHARS_BACKREF:/(\\?)((\W)(\3*))/g,REGEX_SPECIAL_CHARS_GLOBAL:/([-*+?.^${}(|)[\]])/g,REGEX_REMOVE_BACKSLASH:/(?:\[.*?[^\\]\]|\\(?=.))/g,REPLACEMENTS:{"***":"*","**/**":"**","**/**/**":"**"},CHAR_0:48,CHAR_9:57,CHAR_UPPERCASE_A:65,CHAR_LOWERCASE_A:97,CHAR_UPPERCASE_Z:90,CHAR_LOWERCASE_Z:122,CHAR_LEFT_PARENTHESES:40,CHAR_RIGHT_PARENTHESES:41,CHAR_ASTERISK:42,CHAR_AMPERSAND:38,CHAR_AT:64,CHAR_BACKWARD_SLASH:92,CHAR_CARRIAGE_RETURN:13,CHAR_CIRCUMFLEX_ACCENT:94,CHAR_COLON:58,CHAR_COMMA:44,CHAR_DOT:46,CHAR_DOUBLE_QUOTE:34,CHAR_EQUAL:61,CHAR_EXCLAMATION_MARK:33,CHAR_FORM_FEED:12,CHAR_FORWARD_SLASH:47,CHAR_GRAVE_ACCENT:96,CHAR_HASH:35,CHAR_HYPHEN_MINUS:45,CHAR_LEFT_ANGLE_BRACKET:60,CHAR_LEFT_CURLY_BRACE:123,CHAR_LEFT_SQUARE_BRACKET:91,CHAR_LINE_FEED:10,CHAR_NO_BREAK_SPACE:160,CHAR_PERCENT:37,CHAR_PLUS:43,CHAR_QUESTION_MARK:63,CHAR_RIGHT_ANGLE_BRACKET:62,CHAR_RIGHT_CURLY_BRACE:125,CHAR_RIGHT_SQUARE_BRACKET:93,CHAR_SEMICOLON:59,CHAR_SINGLE_QUOTE:39,CHAR_SPACE:32,CHAR_TAB:9,CHAR_UNDERSCORE:95,CHAR_VERTICAL_LINE:124,CHAR_ZERO_WIDTH_NOBREAK_SPACE:65279,SEP:fn.sep,extglobChars(e){return{"!":{type:"negate",open:"(?:(?!(?:",close:`))${e.STAR})`},"?":{type:"qmark",open:"(?:",close:")?"},"+":{type:"plus",open:"(?:",close:")+"},"*":{type:"star",open:"(?:",close:")*"},"@":{type:"at",open:"(?:",close:")"}}},globChars(e){return e===!0?_n:jt}}});var Ae=U(Q=>{"use strict";var bn=W("path"),Sn=process.platform==="win32",{REGEX_BACKSLASH:vn,REGEX_REMOVE_BACKSLASH:Hn,REGEX_SPECIAL_CHARS:wn,REGEX_SPECIAL_CHARS_GLOBAL:Tn}=Re();Q.isObject=e=>e!==null&&typeof e=="object"&&!Array.isArray(e);Q.hasRegexChars=e=>wn.test(e);Q.isRegexChar=e=>e.length===1&&Q.hasRegexChars(e);Q.escapeRegex=e=>e.replace(Tn,"\\$1");Q.toPosixSlashes=e=>e.replace(vn,"/");Q.removeBackslashes=e=>e.replace(Hn,t=>t==="\\"?"":t);Q.supportsLookbehinds=()=>{let e=process.version.slice(1).split(".").map(Number);return e.length===3&&e[0]>=9||e[0]===8&&e[1]>=10};Q.isWindows=e=>e&&typeof e.windows=="boolean"?e.windows:Sn===!0||bn.sep==="\\";Q.escapeLast=(e,t,r)=>{let n=e.lastIndexOf(t,r);return n===-1?e:e[n-1]==="\\"?Q.escapeLast(e,t,n-1):`${e.slice(0,n)}\\${e.slice(n)}`};Q.removePrefix=(e,t={})=>{let r=e;return r.startsWith("./")&&(r=r.slice(2),t.prefix="./"),r};Q.wrapOutput=(e,t={},r={})=>{let n=r.contains?"":"^",s=r.contains?"":"$",a=`${n}(?:${e})${s}`;return t.negated===!0&&(a=`(?:^(?!${a}).*$)`),a}});var Yt=U((ls,Vt)=>{"use strict";var qt=Ae(),{CHAR_ASTERISK:Ge,CHAR_AT:$n,CHAR_BACKWARD_SLASH:me,CHAR_COMMA:Ln,CHAR_DOT:Fe,CHAR_EXCLAMATION_MARK:je,CHAR_FORWARD_SLASH:Wt,CHAR_LEFT_CURLY_BRACE:Ke,CHAR_LEFT_PARENTHESES:qe,CHAR_LEFT_SQUARE_BRACKET:On,CHAR_PLUS:kn,CHAR_QUESTION_MARK:Qt,CHAR_RIGHT_CURLY_BRACE:Nn,CHAR_RIGHT_PARENTHESES:Xt,CHAR_RIGHT_SQUARE_BRACKET:In}=Re(),zt=e=>e===Wt||e===me,Zt=e=>{e.isPrefix!==!0&&(e.depth=e.isGlobstar?Infinity:1)},Pn=(e,t)=>{let r=t||{},n=e.length-1,s=r.parts===!0||r.scanToEnd===!0,a=[],i=[],o=[],f=e,y=-1,h=0,R=0,p=!1,H=!1,m=!1,A=!1,E=!1,b=!1,O=!1,N=!1,J=!1,G=!1,ie=0,F,C,v={value:"",depth:0,isGlob:!1},B=()=>y>=n,l=()=>f.charCodeAt(y+1),$=()=>(F=C,f.charCodeAt(++y));for(;y0&&(oe=f.slice(0,h),f=f.slice(h),R-=h),w&&m===!0&&R>0?(w=f.slice(0,R),u=f.slice(R)):m===!0?(w="",u=f):w=f,w&&w!==""&&w!=="/"&&w!==f&&zt(w.charCodeAt(w.length-1))&&(w=w.slice(0,-1)),r.unescape===!0&&(u&&(u=qt.removeBackslashes(u)),w&&O===!0&&(w=qt.removeBackslashes(w)));let c={prefix:oe,input:e,start:h,base:w,glob:u,isBrace:p,isBracket:H,isGlob:m,isExtglob:A,isGlobstar:E,negated:N,negatedExtglob:J};if(r.tokens===!0&&(c.maxDepth=0,zt(C)||i.push(v),c.tokens=i),r.parts===!0||r.tokens===!0){let j;for(let S=0;S{"use strict";var $e=Re(),Z=Ae(),{MAX_LENGTH:Le,POSIX_REGEX_SOURCE:Dn,REGEX_NON_SPECIAL_CHARS:Mn,REGEX_SPECIAL_CHARS_BACKREF:Bn,REPLACEMENTS:Jt}=$e,Un=(e,t)=>{if(typeof t.expandRange=="function")return t.expandRange(...e,t);e.sort();let r=`[${e.join("-")}]`;try{new RegExp(r)}catch(n){return e.map(s=>Z.escapeRegex(s)).join("..")}return r},fe=(e,t)=>`Missing ${e}: "${t}" - use "\\\\${t}" to match literal characters`,er=(e,t)=>{if(typeof e!="string")throw new TypeError("Expected a string");e=Jt[e]||e;let r=k({},t),n=typeof r.maxLength=="number"?Math.min(Le,r.maxLength):Le,s=e.length;if(s>n)throw new SyntaxError(`Input length: ${s}, exceeds maximum allowed length: ${n}`);let a={type:"bos",value:"",output:r.prepend||""},i=[a],o=r.capture?"":"?:",f=Z.isWindows(t),y=$e.globChars(f),h=$e.extglobChars(y),{DOT_LITERAL:R,PLUS_LITERAL:p,SLASH_LITERAL:H,ONE_CHAR:m,DOTS_SLASH:A,NO_DOT:E,NO_DOT_SLASH:b,NO_DOTS_SLASH:O,QMARK:N,QMARK_NO_DOT:J,STAR:G,START_ANCHOR:ie}=y,F=g=>`(${o}(?:(?!${ie}${g.dot?A:R}).)*?)`,C=r.dot?"":E,v=r.dot?N:J,B=r.bash===!0?F(r):G;r.capture&&(B=`(${B})`),typeof r.noext=="boolean"&&(r.noextglob=r.noext);let l={input:e,index:-1,start:0,dot:r.dot===!0,consumed:"",output:"",prefix:"",backtrack:!1,negated:!1,brackets:0,braces:0,parens:0,quotes:0,globstar:!1,tokens:i};e=Z.removePrefix(e,l),s=e.length;let $=[],w=[],oe=[],u=a,c,j=()=>l.index===s-1,S=l.peek=(g=1)=>e[l.index+g],ee=l.advance=()=>e[++l.index]||"",te=()=>e.slice(l.index+1),X=(g="",T=0)=>{l.consumed+=g,l.index+=T},_e=g=>{l.output+=g.output!=null?g.output:g.value,X(g.value)},Ar=()=>{let g=1;for(;S()==="!"&&(S(2)!=="("||S(3)==="?");)ee(),l.start++,g++;return g%2==0?!1:(l.negated=!0,l.start++,!0)},xe=g=>{l[g]++,oe.push(g)},ue=g=>{l[g]--,oe.pop()},x=g=>{if(u.type==="globstar"){let T=l.braces>0&&(g.type==="comma"||g.type==="brace"),d=g.extglob===!0||$.length&&(g.type==="pipe"||g.type==="paren");g.type!=="slash"&&g.type!=="paren"&&!T&&!d&&(l.output=l.output.slice(0,-u.output.length),u.type="star",u.value="*",u.output=B,l.output+=u.output)}if($.length&&g.type!=="paren"&&($[$.length-1].inner+=g.value),(g.value||g.output)&&_e(g),u&&u.type==="text"&&g.type==="text"){u.value+=g.value,u.output=(u.output||"")+g.value;return}g.prev=u,i.push(g),u=g},be=(g,T)=>{let d=q(k({},h[T]),{conditions:1,inner:""});d.prev=u,d.parens=l.parens,d.output=l.output;let _=(r.capture?"(":"")+d.open;xe("parens"),x({type:g,value:T,output:l.output?"":m}),x({type:"paren",extglob:!0,value:ee(),output:_}),$.push(d)},mr=g=>{let T=g.close+(r.capture?")":""),d;if(g.type==="negate"){let _=B;g.inner&&g.inner.length>1&&g.inner.includes("/")&&(_=F(r)),(_!==B||j()||/^\)+$/.test(te()))&&(T=g.close=`)$))${_}`),g.inner.includes("*")&&(d=te())&&/^\.[^\\/.]+$/.test(d)&&(T=g.close=`)${d})${_})`),g.prev.type==="bos"&&(l.negatedExtglob=!0)}x({type:"paren",extglob:!0,value:c,output:T}),ue("parens")};if(r.fastpaths!==!1&&!/(^[*!]|[/()[\]{}"])/.test(e)){let g=!1,T=e.replace(Bn,(d,_,I,K,M,ke)=>K==="\\"?(g=!0,d):K==="?"?_?_+K+(M?N.repeat(M.length):""):ke===0?v+(M?N.repeat(M.length):""):N.repeat(I.length):K==="."?R.repeat(I.length):K==="*"?_?_+K+(M?B:""):B:_?d:`\\${d}`);return g===!0&&(r.unescape===!0?T=T.replace(/\\/g,""):T=T.replace(/\\+/g,d=>d.length%2==0?"\\\\":d?"\\":"")),T===e&&r.contains===!0?(l.output=e,l):(l.output=Z.wrapOutput(T,l,t),l)}for(;!j();){if(c=ee(),c==="\0")continue;if(c==="\\"){let d=S();if(d==="/"&&r.bash!==!0||d==="."||d===";")continue;if(!d){c+="\\",x({type:"text",value:c});continue}let _=/^\\+/.exec(te()),I=0;if(_&&_[0].length>2&&(I=_[0].length,l.index+=I,I%2!=0&&(c+="\\")),r.unescape===!0?c=ee():c+=ee(),l.brackets===0){x({type:"text",value:c});continue}}if(l.brackets>0&&(c!=="]"||u.value==="["||u.value==="[^")){if(r.posix!==!1&&c===":"){let d=u.value.slice(1);if(d.includes("[")&&(u.posix=!0,d.includes(":"))){let _=u.value.lastIndexOf("["),I=u.value.slice(0,_),K=u.value.slice(_+2),M=Dn[K];if(M){u.value=I+M,l.backtrack=!0,ee(),!a.output&&i.indexOf(u)===1&&(a.output=m);continue}}}(c==="["&&S()!==":"||c==="-"&&S()==="]")&&(c=`\\${c}`),c==="]"&&(u.value==="["||u.value==="[^")&&(c=`\\${c}`),r.posix===!0&&c==="!"&&u.value==="["&&(c="^"),u.value+=c,_e({value:c});continue}if(l.quotes===1&&c!=='"'){c=Z.escapeRegex(c),u.value+=c,_e({value:c});continue}if(c==='"'){l.quotes=l.quotes===1?0:1,r.keepQuotes===!0&&x({type:"text",value:c});continue}if(c==="("){xe("parens"),x({type:"paren",value:c});continue}if(c===")"){if(l.parens===0&&r.strictBrackets===!0)throw new SyntaxError(fe("opening","("));let d=$[$.length-1];if(d&&l.parens===d.parens+1){mr($.pop());continue}x({type:"paren",value:c,output:l.parens?")":"\\)"}),ue("parens");continue}if(c==="["){if(r.nobracket===!0||!te().includes("]")){if(r.nobracket!==!0&&r.strictBrackets===!0)throw new SyntaxError(fe("closing","]"));c=`\\${c}`}else xe("brackets");x({type:"bracket",value:c});continue}if(c==="]"){if(r.nobracket===!0||u&&u.type==="bracket"&&u.value.length===1){x({type:"text",value:c,output:`\\${c}`});continue}if(l.brackets===0){if(r.strictBrackets===!0)throw new SyntaxError(fe("opening","["));x({type:"text",value:c,output:`\\${c}`});continue}ue("brackets");let d=u.value.slice(1);if(u.posix!==!0&&d[0]==="^"&&!d.includes("/")&&(c=`/${c}`),u.value+=c,_e({value:c}),r.literalBrackets===!1||Z.hasRegexChars(d))continue;let _=Z.escapeRegex(u.value);if(l.output=l.output.slice(0,-u.value.length),r.literalBrackets===!0){l.output+=_,u.value=_;continue}u.value=`(${o}${_}|${u.value})`,l.output+=u.value;continue}if(c==="{"&&r.nobrace!==!0){xe("braces");let d={type:"brace",value:c,output:"(",outputIndex:l.output.length,tokensIndex:l.tokens.length};w.push(d),x(d);continue}if(c==="}"){let d=w[w.length-1];if(r.nobrace===!0||!d){x({type:"text",value:c,output:c});continue}let _=")";if(d.dots===!0){let I=i.slice(),K=[];for(let M=I.length-1;M>=0&&(i.pop(),I[M].type!=="brace");M--)I[M].type!=="dots"&&K.unshift(I[M].value);_=Un(K,r),l.backtrack=!0}if(d.comma!==!0&&d.dots!==!0){let I=l.output.slice(0,d.outputIndex),K=l.tokens.slice(d.tokensIndex);d.value=d.output="\\{",c=_="\\}",l.output=I;for(let M of K)l.output+=M.output||M.value}x({type:"brace",value:c,output:_}),ue("braces"),w.pop();continue}if(c==="|"){$.length>0&&$[$.length-1].conditions++,x({type:"text",value:c});continue}if(c===","){let d=c,_=w[w.length-1];_&&oe[oe.length-1]==="braces"&&(_.comma=!0,d="|"),x({type:"comma",value:c,output:d});continue}if(c==="/"){if(u.type==="dot"&&l.index===l.start+1){l.start=l.index+1,l.consumed="",l.output="",i.pop(),u=a;continue}x({type:"slash",value:c,output:H});continue}if(c==="."){if(l.braces>0&&u.type==="dot"){u.value==="."&&(u.output=R);let d=w[w.length-1];u.type="dots",u.output+=c,u.value+=c,d.dots=!0;continue}if(l.braces+l.parens===0&&u.type!=="bos"&&u.type!=="slash"){x({type:"text",value:c,output:R});continue}x({type:"dot",value:c,output:R});continue}if(c==="?"){if(!(u&&u.value==="(")&&r.noextglob!==!0&&S()==="("&&S(2)!=="?"){be("qmark",c);continue}if(u&&u.type==="paren"){let _=S(),I=c;if(_==="<"&&!Z.supportsLookbehinds())throw new Error("Node.js v10 or higher is required for regex lookbehinds");(u.value==="("&&!/[!=<:]/.test(_)||_==="<"&&!/<([!=]|\w+>)/.test(te()))&&(I=`\\${c}`),x({type:"text",value:c,output:I});continue}if(r.dot!==!0&&(u.type==="slash"||u.type==="bos")){x({type:"qmark",value:c,output:J});continue}x({type:"qmark",value:c,output:N});continue}if(c==="!"){if(r.noextglob!==!0&&S()==="("&&(S(2)!=="?"||!/[!=<:]/.test(S(3)))){be("negate",c);continue}if(r.nonegate!==!0&&l.index===0){Ar();continue}}if(c==="+"){if(r.noextglob!==!0&&S()==="("&&S(2)!=="?"){be("plus",c);continue}if(u&&u.value==="("||r.regex===!1){x({type:"plus",value:c,output:p});continue}if(u&&(u.type==="bracket"||u.type==="paren"||u.type==="brace")||l.parens>0){x({type:"plus",value:c});continue}x({type:"plus",value:p});continue}if(c==="@"){if(r.noextglob!==!0&&S()==="("&&S(2)!=="?"){x({type:"at",extglob:!0,value:c,output:""});continue}x({type:"text",value:c});continue}if(c!=="*"){(c==="$"||c==="^")&&(c=`\\${c}`);let d=Mn.exec(te());d&&(c+=d[0],l.index+=d[0].length),x({type:"text",value:c});continue}if(u&&(u.type==="globstar"||u.star===!0)){u.type="star",u.star=!0,u.value+=c,u.output=B,l.backtrack=!0,l.globstar=!0,X(c);continue}let g=te();if(r.noextglob!==!0&&/^\([^?]/.test(g)){be("star",c);continue}if(u.type==="star"){if(r.noglobstar===!0){X(c);continue}let d=u.prev,_=d.prev,I=d.type==="slash"||d.type==="bos",K=_&&(_.type==="star"||_.type==="globstar");if(r.bash===!0&&(!I||g[0]&&g[0]!=="/")){x({type:"star",value:c,output:""});continue}let M=l.braces>0&&(d.type==="comma"||d.type==="brace"),ke=$.length&&(d.type==="pipe"||d.type==="paren");if(!I&&d.type!=="paren"&&!M&&!ke){x({type:"star",value:c,output:""});continue}for(;g.slice(0,3)==="/**";){let Se=e[l.index+4];if(Se&&Se!=="/")break;g=g.slice(3),X("/**",3)}if(d.type==="bos"&&j()){u.type="globstar",u.value+=c,u.output=F(r),l.output=u.output,l.globstar=!0,X(c);continue}if(d.type==="slash"&&d.prev.type!=="bos"&&!K&&j()){l.output=l.output.slice(0,-(d.output+u.output).length),d.output=`(?:${d.output}`,u.type="globstar",u.output=F(r)+(r.strictSlashes?")":"|$)"),u.value+=c,l.globstar=!0,l.output+=d.output+u.output,X(c);continue}if(d.type==="slash"&&d.prev.type!=="bos"&&g[0]==="/"){let Se=g[1]!==void 0?"|$":"";l.output=l.output.slice(0,-(d.output+u.output).length),d.output=`(?:${d.output}`,u.type="globstar",u.output=`${F(r)}${H}|${H}${Se})`,u.value+=c,l.output+=d.output+u.output,l.globstar=!0,X(c+ee()),x({type:"slash",value:"/",output:""});continue}if(d.type==="bos"&&g[0]==="/"){u.type="globstar",u.value+=c,u.output=`(?:^|${H}|${F(r)}${H})`,l.output=u.output,l.globstar=!0,X(c+ee()),x({type:"slash",value:"/",output:""});continue}l.output=l.output.slice(0,-u.output.length),u.type="globstar",u.output=F(r),u.value+=c,l.output+=u.output,l.globstar=!0,X(c);continue}let T={type:"star",value:c,output:B};if(r.bash===!0){T.output=".*?",(u.type==="bos"||u.type==="slash")&&(T.output=C+T.output),x(T);continue}if(u&&(u.type==="bracket"||u.type==="paren")&&r.regex===!0){T.output=c,x(T);continue}(l.index===l.start||u.type==="slash"||u.type==="dot")&&(u.type==="dot"?(l.output+=b,u.output+=b):r.dot===!0?(l.output+=O,u.output+=O):(l.output+=C,u.output+=C),S()!=="*"&&(l.output+=m,u.output+=m)),x(T)}for(;l.brackets>0;){if(r.strictBrackets===!0)throw new SyntaxError(fe("closing","]"));l.output=Z.escapeLast(l.output,"["),ue("brackets")}for(;l.parens>0;){if(r.strictBrackets===!0)throw new SyntaxError(fe("closing",")"));l.output=Z.escapeLast(l.output,"("),ue("parens")}for(;l.braces>0;){if(r.strictBrackets===!0)throw new SyntaxError(fe("closing","}"));l.output=Z.escapeLast(l.output,"{"),ue("braces")}if(r.strictSlashes!==!0&&(u.type==="star"||u.type==="bracket")&&x({type:"maybe_slash",value:"",output:`${H}?`}),l.backtrack===!0){l.output="";for(let g of l.tokens)l.output+=g.output!=null?g.output:g.value,g.suffix&&(l.output+=g.suffix)}return l};er.fastpaths=(e,t)=>{let r=k({},t),n=typeof r.maxLength=="number"?Math.min(Le,r.maxLength):Le,s=e.length;if(s>n)throw new SyntaxError(`Input length: ${s}, exceeds maximum allowed length: ${n}`);e=Jt[e]||e;let a=Z.isWindows(t),{DOT_LITERAL:i,SLASH_LITERAL:o,ONE_CHAR:f,DOTS_SLASH:y,NO_DOT:h,NO_DOTS:R,NO_DOTS_SLASH:p,STAR:H,START_ANCHOR:m}=$e.globChars(a),A=r.dot?R:h,E=r.dot?p:h,b=r.capture?"":"?:",O={negated:!1,prefix:""},N=r.bash===!0?".*?":H;r.capture&&(N=`(${N})`);let J=C=>C.noglobstar===!0?N:`(${b}(?:(?!${m}${C.dot?y:i}).)*?)`,G=C=>{switch(C){case"*":return`${A}${f}${N}`;case".*":return`${i}${f}${N}`;case"*.*":return`${A}${N}${i}${f}${N}`;case"*/*":return`${A}${N}${o}${f}${E}${N}`;case"**":return A+J(r);case"**/*":return`(?:${A}${J(r)}${o})?${E}${f}${N}`;case"**/*.*":return`(?:${A}${J(r)}${o})?${E}${N}${i}${f}${N}`;case"**/.*":return`(?:${A}${J(r)}${o})?${i}${f}${N}`;default:{let v=/^(.*?)\.(\w+)$/.exec(C);if(!v)return;let B=G(v[1]);return B?B+i+v[2]:void 0}}},ie=Z.removePrefix(e,O),F=G(ie);return F&&r.strictSlashes!==!0&&(F+=`${o}?`),F};tr.exports=er});var sr=U((fs,nr)=>{"use strict";var Gn=W("path"),Fn=Yt(),We=rr(),Qe=Ae(),jn=Re(),Kn=e=>e&&typeof e=="object"&&!Array.isArray(e),P=(e,t,r=!1)=>{if(Array.isArray(e)){let h=e.map(p=>P(p,t,r));return p=>{for(let H of h){let m=H(p);if(m)return m}return!1}}let n=Kn(e)&&e.tokens&&e.input;if(e===""||typeof e!="string"&&!n)throw new TypeError("Expected pattern to be a non-empty string");let s=t||{},a=Qe.isWindows(t),i=n?P.compileRe(e,t):P.makeRe(e,t,!1,!0),o=i.state;delete i.state;let f=()=>!1;if(s.ignore){let h=q(k({},t),{ignore:null,onMatch:null,onResult:null});f=P(s.ignore,h,r)}let y=(h,R=!1)=>{let{isMatch:p,match:H,output:m}=P.test(h,i,t,{glob:e,posix:a}),A={glob:e,state:o,regex:i,posix:a,input:h,output:m,match:H,isMatch:p};return typeof s.onResult=="function"&&s.onResult(A),p===!1?(A.isMatch=!1,R?A:!1):f(h)?(typeof s.onIgnore=="function"&&s.onIgnore(A),A.isMatch=!1,R?A:!1):(typeof s.onMatch=="function"&&s.onMatch(A),R?A:!0)};return r&&(y.state=o),y};P.test=(e,t,r,{glob:n,posix:s}={})=>{if(typeof e!="string")throw new TypeError("Expected input to be a string");if(e==="")return{isMatch:!1,output:""};let a=r||{},i=a.format||(s?Qe.toPosixSlashes:null),o=e===n,f=o&&i?i(e):e;return o===!1&&(f=i?i(e):e,o=f===n),(o===!1||a.capture===!0)&&(a.matchBase===!0||a.basename===!0?o=P.matchBase(e,t,r,s):o=t.exec(f)),{isMatch:Boolean(o),match:o,output:f}};P.matchBase=(e,t,r,n=Qe.isWindows(r))=>(t instanceof RegExp?t:P.makeRe(t,r)).test(Gn.basename(e));P.isMatch=(e,t,r)=>P(t,r)(e);P.parse=(e,t)=>Array.isArray(e)?e.map(r=>P.parse(r,t)):We(e,q(k({},t),{fastpaths:!1}));P.scan=(e,t)=>Fn(e,t);P.compileRe=(e,t,r=!1,n=!1)=>{if(r===!0)return e.output;let s=t||{},a=s.contains?"":"^",i=s.contains?"":"$",o=`${a}(?:${e.output})${i}`;e&&e.negated===!0&&(o=`^(?!${o}).*$`);let f=P.toRegex(o,t);return n===!0&&(f.state=e),f};P.makeRe=(e,t={},r=!1,n=!1)=>{if(!e||typeof e!="string")throw new TypeError("Expected a non-empty string");let s={negated:!1,fastpaths:!0};return t.fastpaths!==!1&&(e[0]==="."||e[0]==="*")&&(s.output=We.fastpaths(e,t)),s.output||(s=We(e,t)),P.compileRe(s,t,r,n)};P.toRegex=(e,t)=>{try{let r=t||{};return new RegExp(e,r.flags||(r.nocase?"i":""))}catch(r){if(t&&t.debug===!0)throw r;return/$^/}};P.constants=jn;nr.exports=P});var ir=U((hs,ar)=>{"use strict";ar.exports=sr()});var pr=U((ds,lr)=>{"use strict";var or=W("util"),ur=Bt(),se=ir(),Xe=Ae(),cr=e=>e===""||e==="./",L=(e,t,r)=>{t=[].concat(t),e=[].concat(e);let n=new Set,s=new Set,a=new Set,i=0,o=h=>{a.add(h.output),r&&r.onResult&&r.onResult(h)};for(let h=0;h!n.has(h));if(r&&y.length===0){if(r.failglob===!0)throw new Error(`No matches found for "${t.join(", ")}"`);if(r.nonull===!0||r.nullglob===!0)return r.unescape?t.map(h=>h.replace(/\\/g,"")):t}return y};L.match=L;L.matcher=(e,t)=>se(e,t);L.isMatch=(e,t,r)=>se(t,r)(e);L.any=L.isMatch;L.not=(e,t,r={})=>{t=[].concat(t).map(String);let n=new Set,s=[],a=o=>{r.onResult&&r.onResult(o),s.push(o.output)},i=L(e,t,q(k({},r),{onResult:a}));for(let o of s)i.includes(o)||n.add(o);return[...n]};L.contains=(e,t,r)=>{if(typeof e!="string")throw new TypeError(`Expected a string: "${or.inspect(e)}"`);if(Array.isArray(t))return t.some(n=>L.contains(e,n,r));if(typeof t=="string"){if(cr(e)||cr(t))return!1;if(e.includes(t)||e.startsWith("./")&&e.slice(2).includes(t))return!0}return L.isMatch(e,t,q(k({},r),{contains:!0}))};L.matchKeys=(e,t,r)=>{if(!Xe.isObject(e))throw new TypeError("Expected the first argument to be an object");let n=L(Object.keys(e),t,r),s={};for(let a of n)s[a]=e[a];return s};L.some=(e,t,r)=>{let n=[].concat(e);for(let s of[].concat(t)){let a=se(String(s),r);if(n.some(i=>a(i)))return!0}return!1};L.every=(e,t,r)=>{let n=[].concat(e);for(let s of[].concat(t)){let a=se(String(s),r);if(!n.every(i=>a(i)))return!1}return!0};L.all=(e,t,r)=>{if(typeof e!="string")throw new TypeError(`Expected a string: "${or.inspect(e)}"`);return[].concat(t).every(n=>se(n,r)(e))};L.capture=(e,t,r)=>{let n=Xe.isWindows(r),a=se.makeRe(String(e),q(k({},r),{capture:!0})).exec(n?Xe.toPosixSlashes(t):t);if(a)return a.slice(1).map(i=>i===void 0?"":i)};L.makeRe=(...e)=>se.makeRe(...e);L.scan=(...e)=>se.scan(...e);L.parse=(e,t)=>{let r=[];for(let n of[].concat(e||[]))for(let s of ur(String(n),t))r.push(se.parse(s,t));return r};L.braces=(e,t)=>{if(typeof e!="string")throw new TypeError("Expected a string");return t&&t.nobrace===!0||!/\{.*\}/.test(e)?[e]:ur(e,t)};L.braceExpand=(e,t)=>{if(typeof e!="string")throw new TypeError("Expected a string");return L.braces(e,q(k({},t),{expand:!0}))};lr.exports=L});var Zn={};wr(Zn,{default:()=>zn});var Oe=re(W("@yarnpkg/cli")),D=re(W("@yarnpkg/core")),Y=re(W("clipanion")),Rr=re(pr()),Ye=re(W("semver")),Je=re(W("typanion"));var he=re(W("@yarnpkg/core")),gr=re(W("@yarnpkg/plugin-essentials"));var Ce=re(W("semver")),fr=Boolean;function qn(e){var s;let[t,r,n]=(s=e.match(/(github|bitbucket|gitlab):(.+)/))!=null?s:[];return r?`https://${r}.${r==="bitbucket"?"org":"com"}/${n}`:`https://github.com/${e}`}function hr(e){let{homepage:t,repository:r}=e.raw;return t||(typeof r=="string"?qn(r):r==null?void 0:r.url)}function dr(e,t){return Ce.default.parse(t).prerelease.length?Ce.default.lt(e,t):Ce.default.lt(Ce.default.coerce(e),t)}var ze=class{constructor(t,r,n,s){this.configuration=t;this.project=r;this.workspace=n;this.cache=s}async fetch({pkg:t,range:r,url:n}){let s=gr.suggestUtils.fetchDescriptorFrom(t,r,{cache:this.cache,preserveModifier:!1,project:this.project,workspace:this.workspace}),a=n?this.fetchURL(t):Promise.resolve(void 0),[i,o]=await Promise.all([s,a]);if(!i){let f=he.structUtils.prettyIdent(this.configuration,t);throw new Error(`Could not fetch candidate for ${f}.`)}return{url:o,version:i.range}}async fetchURL(t){var a;let r=this.configuration.makeFetcher(),n=await r.fetch(t,{cache:this.cache,checksums:this.project.storedChecksums,fetcher:r,project:this.project,report:new he.ThrowReport,skipIntegrityCheck:!0}),s;try{s=await he.Manifest.find(n.prefixPath,{baseFs:n.packageFs})}finally{(a=n.releaseFs)==null||a.call(n)}return hr(s)}};var de=re(W("@yarnpkg/core")),Wn=/^([0-9]+\.)([0-9]+\.)(.+)$/,Qn=["name","current","latest","workspace","type","url"],Ze=class{constructor(t,r,n,s){this.report=t;this.configuration=r;this.dependencies=n;this.extraColumns=s;this.sizes=null;this.headers={current:"Current",latest:"Latest",name:"Package",type:"Package Type",url:"URL",workspace:"Workspace"}}print(){this.sizes=this.getColumnSizes(),this.printHeader(),this.dependencies.forEach(t=>{var n,s;let r=this.getDiffColor(t);this.printRow({current:t.current.padEnd(this.sizes.current),latest:this.formatVersion(t,"latest",r),name:this.applyColor(t.name.padEnd(this.sizes.name),r),type:t.type.padEnd(this.sizes.type),url:(n=t.url)==null?void 0:n.padEnd(this.sizes.url),workspace:(s=t.workspace)==null?void 0:s.padEnd(this.sizes.workspace)})})}applyColor(t,r){return de.formatUtils.pretty(this.configuration,t,r)}formatVersion(t,r,n){let s=t[r].padEnd(this.sizes[r]),a=s.match(Wn);if(!a)return s;let i=["red","yellow","green"].indexOf(n)+1,o=a.slice(1,i).join(""),f=a.slice(i).join("");return o+de.formatUtils.pretty(this.configuration,this.applyColor(f,n),"bold")}getDiffColor(t){return{major:"red",minor:"yellow",patch:"green"}[t.severity]}getColumnSizes(){let t={current:this.headers.current.length,latest:this.headers.latest.length,name:this.headers.name.length,type:this.headers.type.length,url:this.headers.url.length,workspace:this.headers.workspace.length};for(let r of this.dependencies)for(let[n,s]of Object.entries(r)){let a=t[n],i=(s||"").length;t[n]=a>i?a:i}return t}formatColumnHeader(t){return de.formatUtils.pretty(this.configuration,this.headers[t].padEnd(this.sizes[t]),"bold")}printHeader(){this.printRow({current:this.formatColumnHeader("current"),latest:this.formatColumnHeader("latest"),name:this.formatColumnHeader("name"),type:this.formatColumnHeader("type"),url:this.formatColumnHeader("url"),workspace:this.formatColumnHeader("workspace")})}printRow(t){let r=Qn.filter(n=>{var s;return(s=this.extraColumns[n])!=null?s:!0}).map(n=>t[n]).join(" ").trim();this.report.reportInfo(de.MessageName.UNNAMED,r)}};var Ve=["dependencies","devDependencies"],yr=["major","minor","patch"];var Ee=class extends Oe.BaseCommand{constructor(){super(...arguments);this.patterns=Y.Option.Rest();this.workspace=Y.Option.Boolean("-w,--workspace",!1,{description:"Only include outdated dependencies in the current workspace"});this.check=Y.Option.Boolean("-c,--check",!1,{description:"Exit with exit code 1 when outdated dependencies are found"});this.json=Y.Option.Boolean("--json",!1,{description:"Format the output as JSON"});this.severity=Y.Option.String("-s,--severity",{description:"Filter results based on the severity of the update",validator:Je.default.isEnum(yr)});this.type=Y.Option.String("-t,--type",{description:"Filter results based on the dependency type",validator:Je.default.isEnum(Ve)});this.url=Y.Option.Boolean("--url",!1,{description:"Include the homepage URL of each package in the output"})}async execute(){let{cache:t,configuration:r,project:n,workspace:s}=await this.loadProject(),a=new ze(r,n,s,t),i=this.getWorkspaces(n,s),o=this.getDependencies(r,i);if(this.json){let y=await this.getOutdatedDependencies(n,a,o);this.context.stdout.write(JSON.stringify(y)+` 8 | `);return}return(await D.StreamReport.start({configuration:r,stdout:this.context.stdout},async y=>{await this.checkOutdatedDependencies(r,n,o,a,y)})).exitCode()}async checkOutdatedDependencies(t,r,n,s,a){let i=null;await a.startTimerPromise("Checking for outdated dependencies",async()=>{let o=n.length,f=D.StreamReport.progressViaCounter(o);a.reportProgress(f),i=await this.getOutdatedDependencies(r,s,n,f)}),a.reportSeparator(),i.length?(new Ze(a,t,i,{url:this.url,workspace:this.includeWorkspace(r)}).print(),a.reportSeparator(),this.printOutdatedCount(a,i.length)):this.printUpToDate(t,a)}async loadProject(){let t=await D.Configuration.find(this.context.cwd,this.context.plugins),[r,{project:n,workspace:s}]=await Promise.all([D.Cache.find(t),D.Project.find(t,this.context.cwd)]);if(await n.restoreInstallState(),!s)throw new Oe.WorkspaceRequiredError(n.cwd,this.context.cwd);return{cache:r,configuration:t,project:n,workspace:s}}getWorkspaces(t,r){return this.workspace?[r]:t.workspaces}includeWorkspace(t){return!this.workspace&&t.workspaces.length>1}get dependencyTypes(){return this.type?[this.type]:Ve}getDependencies(t,r){let n=[];for(let a of r){let{anchoredLocator:i,project:o}=a,f=o.storedPackages.get(i.locatorHash);f||this.throw(t,i);for(let y of this.dependencyTypes)for(let h of a.manifest[y].values()){let{range:R}=h;if(R.includes(":")&&!/(npm|patch):/.test(R))continue;let p=f.dependencies.get(h.identHash);p||this.throw(t,h);let H=o.storedResolutions.get(p.descriptorHash);H||this.throw(t,p);let m=o.storedPackages.get(H);m||this.throw(t,p),n.push({dependencyType:y,name:D.structUtils.stringifyIdent(h),pkg:m,workspace:a})}}if(!this.patterns.length)return n;let s=n.filter(({name:a})=>Rr.default.isMatch(a,this.patterns));if(!s.length)throw new Y.UsageError(`Pattern ${D.formatUtils.prettyList(t,this.patterns,D.FormatType.CODE)} doesn't match any packages referenced by any workspace`);return s}throw(t,r){let n=D.structUtils.prettyIdent(t,r);throw new Error(`Package for ${n} not found in the project`)}getSeverity(t,r){let n=Ye.default.coerce(t),s=Ye.default.coerce(r);return n.major===0||s.major>n.major?"major":s.minor>n.minor?"minor":"patch"}async getOutdatedDependencies(t,r,n,s){let a=n.map(async({dependencyType:i,name:o,pkg:f,workspace:y})=>{if(y.project.tryWorkspaceByLocator(f))return;let{url:h,version:R}=await r.fetch({pkg:f,range:"latest",url:this.url});if(s==null||s.tick(),dr(f.version,R))return{current:f.version,latest:R,name:o,severity:this.getSeverity(f.version,R),type:i,url:h,workspace:this.includeWorkspace(t)?this.getWorkspaceName(y):void 0}});return(await Promise.all(a)).filter(fr).filter(({severity:i})=>!this.severity||i===this.severity).sort((i,o)=>i.name.localeCompare(o.name))}getWorkspaceName(t){return t.manifest.name?D.structUtils.stringifyIdent(t.manifest.name):t.computeCandidateName()}printOutdatedCount(t,r){let n=[D.MessageName.UNNAMED,r===1?"1 dependency is out of date":`${r} dependencies are out of date`];this.check?t.reportError(...n):t.reportWarning(...n)}printUpToDate(t,r){let n="\u2728 All your dependencies are up to date!";r.reportInfo(D.MessageName.UNNAMED,D.formatUtils.pretty(t,n,"green"))}};Ee.paths=[["outdated"]],Ee.usage=Y.Command.Usage({description:"view outdated dependencies",details:` 9 | This command finds outdated dependencies in a project and prints the result in a table or JSON format. 10 | 11 | This command accepts glob patterns as arguments to filter the output. Make sure to escape the patterns, to prevent your own shell from trying to expand them. 12 | `,examples:[["View outdated dependencies","yarn outdated"],["View outdated dependencies with the `@babel` scope","yarn outdated '@babel/*'"],["Filter results to only include devDependencies","yarn outdated --type devDependencies"],["Filter results to only include major version updates","yarn outdated --severity major"]]});var Xn={commands:[Ee]},zn=Xn;return Zn;})(); 13 | /*! 14 | * fill-range 15 | * 16 | * Copyright (c) 2014-present, Jon Schlinkert. 17 | * Licensed under the MIT License. 18 | */ 19 | /*! 20 | * is-number 21 | * 22 | * Copyright (c) 2014-present, Jon Schlinkert. 23 | * Released under the MIT License. 24 | */ 25 | /*! 26 | * to-regex-range 27 | * 28 | * Copyright (c) 2015-present, Jon Schlinkert. 29 | * Released under the MIT License. 30 | */ 31 | return plugin; 32 | } 33 | }; 34 | --------------------------------------------------------------------------------