diff --git a/.air.toml b/.air.toml new file mode 100644 index 0000000..c9e8f37 --- /dev/null +++ b/.air.toml @@ -0,0 +1,65 @@ +root = "." +tmp_dir = "tmp" +testdata_dir = "testdata" + +[build] + pre_cmd = ["make generate-required"] + cmd = "go build -buildvcs=false -o ./tmp/main ./cmd/codexgo" + post_cmd = [] + bin = "./tmp/main" + full_bin = "" + args_bin = [] + include_ext = [ + "go", + "html", + "templ", + "tmpl", + "tpl" + ] + exclude_dir = [] + include_dir = [ + "cmd", + "internal", + "pkg" + ] + include_file = [] + exclude_file = [] + exclude_regex = [ + "^.*_test\\.go$", + "^.*\\.mother\\.go$", + "^.*\\.mock\\.go$", + "^.*_templ\\.go$" + ] + exclude_unchanged = true + follow_symlink = true + log = "air.log" + poll = false + poll_interval = 1000 + delay = 1000 + stop_on_error = false + send_interrupt = true + kill_delay = 1000 + rerun = false + rerun_delay = 1000 + +[log] + time = true + main_only = false + +[color] + main = "magenta" + watcher = "cyan" + build = "yellow" + runner = "green" + +[misc] + clean_on_exit = true + +[screen] + clear_on_rebuild = true + keep_scroll = true + +[proxy] + enabled = true + proxy_port = 8090 + app_port = 8080 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..54c7177 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,53 @@ +{ + "name": "codexgo", + "image": "mcr.microsoft.com/devcontainers/base:bookworm", + "features": { + "ghcr.io/devcontainers/features/sshd:1": {}, + "ghcr.io/devcontainers/features/docker-in-docker:2": {}, + "ghcr.io/devcontainers/features/go:1": {}, + "ghcr.io/devcontainers/features/node:1": {} + }, + "forwardPorts": [2222], + "postCreateCommand": "USER_PASSWORD=vscode make devcontainer && make init", + "customizations": { + "vscode": { + "extensions": [ + "a-h.templ", + "aaron-bond.better-comments", + "CucumberOpen.cucumber-official", + "esbenp.prettier-vscode", + "github.vscode-github-actions", + "golang.go", + "Gruntfuggly.todo-tree", + "ms-azuretools.vscode-docker", + "ms-vscode.makefile-tools", + "redhat.vscode-yaml", + "streetsidesoftware.code-spell-checker", + "tamasfe.even-better-toml", + "thejltres.fomantic-ui-snippets" + ], + "settings": { + "[cucumber]": { + "editor.defaultFormatter": "CucumberOpen.cucumber-official" + }, + "[go]": { + "editor.defaultFormatter": "golang.go" + }, + "[templ]": { + "editor.defaultFormatter": "a-h.templ" + }, + "cSpell.language": "en,lorem", + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnPaste": true, + "editor.formatOnSave": true, + "emmet.includeLanguages": { + "templ": "html" + }, + "go.toolsManagement.autoUpdate": true, + "gopls": { + "ui.semanticTokens": true + } + } + } + } +} diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..03303b8 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,59 @@ +#* Editor Directories & Files +.vscode/* +!.vscode/extensions.json +!.vscode/settings.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +*.local + +#* Package Managers +vendor +node_modules +package +pnpm-lock.yaml +yarn.lock +go.work.sum + +#* Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +#* ENV +*.env* +!*.env.demo* +!*.env.example* +*.npmrc* + +#* Database +*.db* +!*.db.demo* +!*.db.example* + +#* Production +dist +dist-ssr +build + +#* Tooling +vite.config.js.timestamp-* +vite.config.ts.timestamp-* +*_templ.txt +*_templ.go + +#* Misc +ignore* +report* +temp* +tmp* + +#* Repository diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..9b77ea7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,40 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "" +labels: "" +assignees: "" +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: + +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + +- OS: [e.g. iOS] +- Browser [e.g. chrome, safari] +- Version [e.g. 22] + +**Smartphone (please complete the following information):** + +- Device: [e.g. iPhone6] +- OS: [e.g. iOS8.1] +- Browser [e.g. stock browser, safari] +- Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..2bc5d5f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,19 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "" +labels: "" +assignees: "" +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml new file mode 100644 index 0000000..855be7e --- /dev/null +++ b/.github/actions/setup/action.yml @@ -0,0 +1,23 @@ +name: Setup + +description: Setup & Caching Dependencies + +runs: + using: "composite" + + steps: + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: "go.mod" + cache-dependency-path: "go.sum" + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version-file: "package.json" + cache: "npm" + + - name: Setup codexGO + shell: ${{ runner.os == 'Windows' && 'pwsh' || 'bash' }} + run: make init-ci diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..64f9d8f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,56 @@ +name: CI + +on: + push: + branches: [main, ci/**] + pull_request: + branches: [main] + +jobs: + TruffleHog: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Secret Scanning + uses: trufflesecurity/trufflehog@main + with: + extra_args: --only-verified + + Lint: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup + uses: ./.github/actions/setup + + - name: Check + run: make lint-check + + Test: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup + uses: ./.github/actions/setup + + - name: Unit + run: make test-unit + + - name: Upload Report + uses: actions/upload-artifact@v4 + if: success() || failure() + with: + name: Test Report + path: ./test/report + retention-days: 30 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..c5bddab --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,64 @@ +name: Release + +on: + workflow_dispatch: + inputs: + status: + type: choice + description: Status + options: + - alpha + - beta + - stable + default: stable + + bump: + type: choice + description: Bump + options: + - patch + - minor + - major + - auto + default: auto + + dry: + type: boolean + description: Dry Release + default: false + +jobs: + Generate: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup + uses: ./.github/actions/setup + + - name: Import GPG Key + id: import-gpg + uses: crazy-max/ghaction-import-gpg@v6 + with: + gpg_private_key: ${{ secrets.BOT_GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.BOT_GPG_PASSPHRASE }} + git_config_global: true + git_user_signingkey: true + git_commit_gpgsign: true + git_tag_gpgsign: true + + - name: Generate Release + run: OPTIONS="$STATUS_ARG $BUMP_ARG $DRY_ARG" make release-ci + env: + STATUS_ARG: ${{ fromJSON('{"alpha":"--preRelease=alpha", "beta":"--preRelease=beta", "stable":""}')[github.event.inputs.status] }} + BUMP_ARG: ${{ fromJSON('{"patch":"-i patch", "minor":"-i minor", "major":"-i major", "auto":""}')[github.event.inputs.bump] }} + DRY_ARG: ${{ github.event.inputs.dry == 'true' && '-d' || '' }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GIT_AUTHOR_NAME: ${{ steps.import-gpg.outputs.name }} + GIT_AUTHOR_EMAIL: ${{ steps.import-gpg.outputs.email }} + GIT_COMMITTER_NAME: ${{ steps.import-gpg.outputs.name }} + GIT_COMMITTER_EMAIL: ${{ steps.import-gpg.outputs.email }} diff --git a/.github/workflows/upgrade.yml b/.github/workflows/upgrade.yml new file mode 100644 index 0000000..97e2934 --- /dev/null +++ b/.github/workflows/upgrade.yml @@ -0,0 +1,38 @@ +name: Upgrade + +on: + workflow_dispatch: + +jobs: + Dependencies: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup + uses: ./.github/actions/setup + + - name: Import GPG Key + id: import-gpg + uses: crazy-max/ghaction-import-gpg@v6 + with: + gpg_private_key: ${{ secrets.BOT_GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.BOT_GPG_PASSPHRASE }} + git_config_global: true + git_user_signingkey: true + git_commit_gpgsign: true + git_tag_gpgsign: true + + - name: Upgrade Dependencies + run: | + make upgrade + git push origin main + env: + GIT_AUTHOR_NAME: ${{ steps.import-gpg.outputs.name }} + GIT_AUTHOR_EMAIL: ${{ steps.import-gpg.outputs.email }} + GIT_COMMITTER_NAME: ${{ steps.import-gpg.outputs.name }} + GIT_COMMITTER_EMAIL: ${{ steps.import-gpg.outputs.email }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..03303b8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,59 @@ +#* Editor Directories & Files +.vscode/* +!.vscode/extensions.json +!.vscode/settings.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +*.local + +#* Package Managers +vendor +node_modules +package +pnpm-lock.yaml +yarn.lock +go.work.sum + +#* Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +#* ENV +*.env* +!*.env.demo* +!*.env.example* +*.npmrc* + +#* Database +*.db* +!*.db.demo* +!*.db.example* + +#* Production +dist +dist-ssr +build + +#* Tooling +vite.config.js.timestamp-* +vite.config.ts.timestamp-* +*_templ.txt +*_templ.go + +#* Misc +ignore* +report* +temp* +tmp* + +#* Repository diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100755 index 0000000..0398b7a --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1 @@ +npx --no -- commitlint --edit ${1} diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..1524ad6 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx --no -- lint-staged diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 0000000..41b8dd7 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1 @@ +make scan-leaks-local diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..03303b8 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,59 @@ +#* Editor Directories & Files +.vscode/* +!.vscode/extensions.json +!.vscode/settings.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +*.local + +#* Package Managers +vendor +node_modules +package +pnpm-lock.yaml +yarn.lock +go.work.sum + +#* Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +#* ENV +*.env* +!*.env.demo* +!*.env.example* +*.npmrc* + +#* Database +*.db* +!*.db.demo* +!*.db.example* + +#* Production +dist +dist-ssr +build + +#* Tooling +vite.config.js.timestamp-* +vite.config.ts.timestamp-* +*_templ.txt +*_templ.go + +#* Misc +ignore* +report* +temp* +tmp* + +#* Repository diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..082bc94 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["ms-vscode-remote.remote-containers"] +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..19909c9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,456 @@ +# Changelog + +## [4.6.1](https://github.com/bastean/codexgo/compare/v4.6.0...v4.6.1) (2024-08-03) + +### Bug Fixes + +- **server:** relocate unreachable logs ([1f2df6a](https://github.com/bastean/codexgo/commit/1f2df6ab02ec362a27233344a9f2035e374bfa00)) + +## [4.6.0](https://github.com/bastean/codexgo/compare/v4.5.0...v4.6.0) (2024-08-03) + +### Chores + +- **deps:** upgrade ([240d8d4](https://github.com/bastean/codexgo/commit/240d8d4ed5334cb8b6561773752c9a83184101b4)) +- **docker:** add ignore file ([9bbaa59](https://github.com/bastean/codexgo/commit/9bbaa598143dfaabb69988a0c503c4550abe004e)) + +### Documentation + +- **readme:** add script to initialize repository from zip file ([78c843b](https://github.com/bastean/codexgo/commit/78c843b12e9bde9870bc758eef2f47783d592171)) + +### New Features + +- **devcontainer:** add ssh server ([5492305](https://github.com/bastean/codexgo/commit/54923055d13b4bde924b65dd10f7d8f95bfb6d49)) + +### Refactors + +- **docker:** rename default network ([494b3b4](https://github.com/bastean/codexgo/commit/494b3b4ad5796f3757ea03bf943c592772e837bc)) +- **makefile:** rename targets ([9093e2d](https://github.com/bastean/codexgo/commit/9093e2db7ba1f0f4d1e3b4ceb5b2167fc8665906)) +- **server:** separate views from api endpoints ([5fdd1ea](https://github.com/bastean/codexgo/commit/5fdd1ea6897493c06bf62f9199ca35cb6454adff)) + +## [4.5.0](https://github.com/bastean/codexgo/compare/v4.4.0...v4.5.0) (2024-07-30) + +### ⚠ BREAKING CHANGES + +- **deployments:** rename envs + +### Chores + +- change git ignore list ([f93dea2](https://github.com/bastean/codexgo/commit/f93dea2bb31c4a1d1dda834a832533a18ba44612)) +- **deps:** upgrade ([df92fda](https://github.com/bastean/codexgo/commit/df92fda92b054363249a9ef86440ad5910fff812)) +- **deps:** upgrade ([c994a97](https://github.com/bastean/codexgo/commit/c994a97ee5e635b37aa7181a22d97010ae22aaa0)) +- **makefile:** add set of rules to install tools ([94f0b8f](https://github.com/bastean/codexgo/commit/94f0b8fb8dcc7241bdd6c94013503bb9a9eed74a)) +- **mod:** upgrade ([31e32d2](https://github.com/bastean/codexgo/commit/31e32d2e250e58d5b1c5c93507ba33fe55bf33ab)) + +### Documentation + +- **readme:** add basic idiomatic ([1af2146](https://github.com/bastean/codexgo/commit/1af2146739b07d3f4682ea56bbefb914b1415d7b)) + +### New Features + +- **logger:** add colored outputs ([9e9d041](https://github.com/bastean/codexgo/commit/9e9d0415dfe982294405f5d5323ddc1fa8d7c22b)) +- **server:** add initial health check endpoint ([95f2117](https://github.com/bastean/codexgo/commit/95f2117797593780f2c77e6cd5900beb1be650b3)) +- **server:** add proxy check ([367773c](https://github.com/bastean/codexgo/commit/367773ce8ea859fe62c81b05e148be2dcb99101d)) + +### Refactors + +- change format of messages in errors ([dda8c9a](https://github.com/bastean/codexgo/commit/dda8c9ae2dfd919a15cfce597356e8026a20b8ce)) +- **deployments:** rename envs ([a3186bd](https://github.com/bastean/codexgo/commit/a3186bd899904d237a20e347940115796e22702f)) +- **dockerfile:** use makefile targets to install the tools ([c77f092](https://github.com/bastean/codexgo/commit/c77f0926af64ec0dc9b6240c61f1a05d27dbdb9e)) +- rename functions according to their layer ([e37b64c](https://github.com/bastean/codexgo/commit/e37b64c6a7744c81f995f821dbbed91b87085dc1)) +- **scripts:** reuse command execution in upgrade ([c48eec6](https://github.com/bastean/codexgo/commit/c48eec66c1143917cfc750fc628407d416246479)) +- **server:** change error handling ([a789393](https://github.com/bastean/codexgo/commit/a789393f07ce92914c27096cdaee7a745de2aba3)) +- **service:** organize envs ([71f4f4c](https://github.com/bastean/codexgo/commit/71f4f4ca29b9c6449612be18c990d8a6592cdece)) +- **service:** use aliases of context types ([51d7701](https://github.com/bastean/codexgo/commit/51d770172f757a4283bed9434d3c13cb8f89dabb)) + +### Tests + +- **acceptance:** assert errors directly ([6471fe2](https://github.com/bastean/codexgo/commit/6471fe2a71f6ec9c235c53fb880bb422e9724b20)) + +## [4.4.0](https://github.com/bastean/codexgo/compare/v4.3.1...v4.4.0) (2024-07-14) + +### ⚠ BREAKING CHANGES + +- **context:** rename packages +- **infrastructure:** rename packages +- rename envs +- add internal folder to manage apps and services + +### Chores + +- **deps:** upgrade dependencies ([dd29c51](https://github.com/bastean/codexgo/commit/dd29c5141e6f43b8fc9c4cb52806074d64cd6c5a)) + +### Documentation + +- **readme:** add more details about the interaction between layers ([99476ba](https://github.com/bastean/codexgo/commit/99476ba7c1d9f01bc8d42d59f5a417c1c43b59c2)) + +### New Features + +- **server:** show proxy port when running ([42c84ed](https://github.com/bastean/codexgo/commit/42c84edb2a2d4f37245b31da512e8e4f924a9fd8)) + +### Refactors + +- add internal folder to manage apps and services ([6081521](https://github.com/bastean/codexgo/commit/6081521f2f4496fcc4b3e162c31e5f9bb8b8c265)) +- **cmd:** stop apps before services ([0b52af7](https://github.com/bastean/codexgo/commit/0b52af71e25e7d0c2e98c0fb7c42cdd9f0148b66)) +- **context:** rename packages ([07f39eb](https://github.com/bastean/codexgo/commit/07f39eb8b477460c3ba15ee99fd893e8e9384232)) +- **context:** replace use of generic models with specific ones ([6ee85b2](https://github.com/bastean/codexgo/commit/6ee85b2aeb12d037e639cdd20c6696b38ae72349)) +- **domain:** add err prefix to bubble errors ([814a73b](https://github.com/bastean/codexgo/commit/814a73b55118751a5d3c7db77a3aa3fd364c57cb)) +- **infrastructure:** rename packages ([aa6b04d](https://github.com/bastean/codexgo/commit/aa6b04d42348cec42425eea5375b63328854eac1)) +- rename envs ([12ab31d](https://github.com/bastean/codexgo/commit/12ab31d7aeadb6129df463f558add5896b519a8b)) + +### Tests + +- **infrastructure:** add assertions for missing mongo errors ([d42e73e](https://github.com/bastean/codexgo/commit/d42e73e45d8b781b9b88c52edf6d7528e4effe4e)) + +## [4.3.1](https://github.com/bastean/codexgo/compare/v4.3.0...v4.3.1) (2024-07-01) + +### Chores + +- **deps:** upgrade dependencies ([1011a81](https://github.com/bastean/codexgo/commit/1011a814c472f1d00b75aa125e05f8b21721db8d)) + +### Bug Fixes + +- **makefile:** remove previous production docker image ([bcd39a0](https://github.com/bastean/codexgo/commit/bcd39a0d1b51eb6314e59c6239454d98dfeaa350)) +- **templ:** resolve imported and not used error ([84cbd69](https://github.com/bastean/codexgo/commit/84cbd69f90eccc3909e0ae620876bc3a1729acab)) + +## [4.3.0](https://github.com/bastean/codexgo/compare/v4.2.1...v4.3.0) (2024-06-26) + +### Chores + +- **deps:** upgrade dependencies ([bce72d8](https://github.com/bastean/codexgo/commit/bce72d872897ad527dd3e435fe45fe5f93300d81)) +- **deps:** upgrade jwt to v5 ([18054b7](https://github.com/bastean/codexgo/commit/18054b747b8250e7a169359dc1f54287328d0edc)) + +### Documentation + +- add scanners ([a34ea61](https://github.com/bastean/codexgo/commit/a34ea616ff1425c3053115df604e64ef9609e283)) + +### New Features + +- add trivy and osv scanners ([6fe3c53](https://github.com/bastean/codexgo/commit/6fe3c53255d3fb68dbbda985a0c15e7b9b68ae5c)) + +### Refactors + +- **context:** change domain message components ([157ef5b](https://github.com/bastean/codexgo/commit/157ef5bcd3a54c9784ddcdd6339e9223da735a38)) +- **context:** rename variables in value objects ([c8f46a1](https://github.com/bastean/codexgo/commit/c8f46a1e857c25d38a72da5a9f30273c8dc64e3d)) +- **scripts:** change panic on error ([4577790](https://github.com/bastean/codexgo/commit/4577790af035df66e18aa96ee8289c30409a1687)) +- **server:** rename service logs ([65bd07c](https://github.com/bastean/codexgo/commit/65bd07c900686326c91d5fba163c501348a8589e)) +- **server:** reorganize services ([f559715](https://github.com/bastean/codexgo/commit/f5597153bd121cce16d77aecba2cf0e268bcf1ce)) + +### Tests + +- **context:** add assertion for duplication error in mongo ([201fd53](https://github.com/bastean/codexgo/commit/201fd53f5970546dddaf62c2b21d50a35841052c)) +- **context:** add assertion for omitted json errors ([a08ea38](https://github.com/bastean/codexgo/commit/a08ea38efd3fa0d191505f48de571a234c9b2217)) + +## [4.2.1](https://github.com/bastean/codexgo/compare/v4.2.0...v4.2.1) (2024-06-19) + +### Bug Fixes + +- add boolean format verb in strings ([e317911](https://github.com/bastean/codexgo/commit/e317911d22e1798a4bc0a1c917089b06d8228a35)) +- remove default format verb in strings ([57beb5e](https://github.com/bastean/codexgo/commit/57beb5e3339a6ac9685e47bd2c798fdcccf619b2)) + +## [4.2.0](https://github.com/bastean/codexgo/compare/v4.1.1...v4.2.0) (2024-06-17) + +### Chores + +- **deps:** upgrade dependencies ([7b19ee7](https://github.com/bastean/codexgo/commit/7b19ee7dee15d14a7fad328977d090c031ef2964)) + +### New Features + +- **context:** add handling of omitted errors ([f70c276](https://github.com/bastean/codexgo/commit/f70c2767a368859560c1c2986411384ee36ae92e)) + +### Refactors + +- add default format verb to strings ([c459caa](https://github.com/bastean/codexgo/commit/c459caaf4c6fb7c5aede730caad10ab0093e3a3e)) +- change panic on error ([1fc83fa](https://github.com/bastean/codexgo/commit/1fc83fa7b6433059e098bbe8a21762b7eb95000a)) +- **context:** remove notify module ([064815f](https://github.com/bastean/codexgo/commit/064815f9efb7b75638c4f2d88efe1a27cacecc80)) +- **context:** remove redundant details from type names ([27a666a](https://github.com/bastean/codexgo/commit/27a666a11bf7232df9d9ba34c20f1d15e03abade)) + +### Tests + +- **context:** add handling of unexpected errors in mothers to avoid flaky tests ([2fbea22](https://github.com/bastean/codexgo/commit/2fbea223fada03944b1deb6a1b83f2f7df879e93)) + +## [4.1.1](https://github.com/bastean/codexgo/compare/v4.1.0...v4.1.1) (2024-06-12) + +### Bug Fixes + +- **makefile:** add pipefail to return an error when a test fails ([5b4c26e](https://github.com/bastean/codexgo/commit/5b4c26e4621fec259eb1b6cfa0bd263534b50588)) + +## [4.1.0](https://github.com/bastean/codexgo/compare/v4.0.0...v4.1.0) (2024-06-10) + +### Chores + +- **deps:** upgrade dependencies ([93fc426](https://github.com/bastean/codexgo/commit/93fc4264ee2f2937e07e9a8f9c96156df4dfb4f1)) + +### Documentation + +- **readme:** add basic layers workflow ([b6f6d5d](https://github.com/bastean/codexgo/commit/b6f6d5d0f8b6bb75759a8a0744f86bd5abc44893)) + +### New Features + +- **makefile:** add tee in test rules ([1d21d7a](https://github.com/bastean/codexgo/commit/1d21d7a31fac96db750c9266e181f93765ca1089)) + +### Bug Fixes + +- **dockerfile:** update air module name ([71bc376](https://github.com/bastean/codexgo/commit/71bc376c7a6e834d16eaf10684806ca652bd3e51)) + +### Refactors + +- add type alias ([f55bb9d](https://github.com/bastean/codexgo/commit/f55bb9d1fbe3c933c1bb48e6885270c92e69beee)) +- **context:** add pointer to search criteria type ([8648a18](https://github.com/bastean/codexgo/commit/8648a184464c355d5b6216a4cb6a2f58c4bc1b95)) +- **context:** change empty type from struct to interface ([4e5dcf0](https://github.com/bastean/codexgo/commit/4e5dcf0152a5eec35818edf90030ae449037e0a2)) +- **context:** change errors in shared module ([47fe621](https://github.com/bastean/codexgo/commit/47fe62172d93792694beef6df3145502690e8d6a)) +- **context:** change parameters to use primitive type in user module ([db8fc5b](https://github.com/bastean/codexgo/commit/db8fc5b12b24968677f2a452e703a5af3192020f)) +- **context:** change updates in user module ([ba294af](https://github.com/bastean/codexgo/commit/ba294aff99ac5a6d35b2701531d97bf16f6191fd)) +- squash struct fields ([8ccc22c](https://github.com/bastean/codexgo/commit/8ccc22c0ed57d02e1717d723462673ee1ebc55d3)) + +### Tests + +- **context:** add more explicit test case names ([5027a31](https://github.com/bastean/codexgo/commit/5027a31a0fafcdbcac17ad09a04b557e145c5a55)) + +## [4.0.0](https://github.com/bastean/codexgo/compare/v3.0.1...v4.0.0) (2024-05-28) + +### ⚠ BREAKING CHANGES + +- **server:** decouple service initializations +- **context:** change notification system workflow +- **server:** change acceptance tests to work with the new ui +- **server:** add fomantic-ui +- **server:** change error handling from panic to wrapped errors +- **context:** change package names in shared module +- **context:** change integration tests to check for wrapped errors instead of panic +- **context:** change unit tests to check for wrapped errors instead of panic +- **context:** change error handling from panic to wrapped errors + +### Chores + +- change air config ([a3b2f94](https://github.com/bastean/codexgo/commit/a3b2f94cb9f1f5b69ab719ae09001bac8382e7b2)) +- change git ignore list ([122f7b2](https://github.com/bastean/codexgo/commit/122f7b2ba448cf79e741e5c7c3e3b7283a2dcaaf)) +- change go version in mod file ([95ac107](https://github.com/bastean/codexgo/commit/95ac107f7b6dee5618ec459c911ca07440521c04)) +- change makefile rules ([285b30b](https://github.com/bastean/codexgo/commit/285b30b7e1c88503681279e097d4a6e0abf8154c)) +- **deps:** upgrade dependencies ([f804fc8](https://github.com/bastean/codexgo/commit/f804fc8c0e22f660407e80c826811455d9d13303)) + +### Documentation + +- **readme:** add updated screenshots ([ffd6b17](https://github.com/bastean/codexgo/commit/ffd6b175b19292111ba5bff3611322c7ee8cdbb6)) +- **readme:** change description ([122b14c](https://github.com/bastean/codexgo/commit/122b14c69174c5de0f9cc0cac54e4b55ea3a25d2)) + +### New Features + +- **air:** enable live-reloading on the browser ([7714c38](https://github.com/bastean/codexgo/commit/7714c38cf0a74ef07dff07d232b98f462070a71a)) +- **context:** add json marshal error handler to error bubble ([68819fe](https://github.com/bastean/codexgo/commit/68819fe9521117adb43504105b90f91abecebce4)) +- **context:** add new terminal transport port adapter to notify module ([28fd1fe](https://github.com/bastean/codexgo/commit/28fd1fe865666b65f25d93e1f7b1895f2b40a998)) +- **scripts:** add copy-deps script ([79e2d73](https://github.com/bastean/codexgo/commit/79e2d73989674b9a2d62841db87da89ef4bd8564)) +- **server:** add accepts cookies nag ([682c370](https://github.com/bastean/codexgo/commit/682c370366139911fa145defa9127310503d86e9)) +- **server:** add cookies cleaning ([30d4b9a](https://github.com/bastean/codexgo/commit/30d4b9aca620098e1ed4b9c0c0501003570f8007)) +- **server:** add fomantic-ui ([738bf51](https://github.com/bastean/codexgo/commit/738bf5140c0dcb310c4effcbd3659720ba2c20a5)) +- **server:** add log files ([141001a](https://github.com/bastean/codexgo/commit/141001a2d0bf0269a25030597c45d3a2b7c2f891)) +- **server:** add missing error handlers ([99938f6](https://github.com/bastean/codexgo/commit/99938f6a5c00a47a5d4471d07354fe565a6c50f5)) +- **server:** add popup to inform about account status ([77eb4a9](https://github.com/bastean/codexgo/commit/77eb4a917e1487a3736da346064befa0ff3d35c8)) + +### Bug Fixes + +- add missing pointers ([b6b9343](https://github.com/bastean/codexgo/commit/b6b934305caecf51eea9c058e84d75cfaa2d353f)) +- **deps:** upgrade dependencies ([4574f31](https://github.com/bastean/codexgo/commit/4574f3147dff657eb1672a97d0acea236bf8d5c4)) +- **server:** add json unmarshal type error handler ([eb9eeb5](https://github.com/bastean/codexgo/commit/eb9eeb5e4442caf2abd07c294cd68a0c9db8f9b9)) + +### Refactors + +- add field names at struct initialization ([55c5de3](https://github.com/bastean/codexgo/commit/55c5de3902be83a9066b9867e9493ad4ebab6f87)) +- **context:** change error handling from panic to wrapped errors ([ec3245c](https://github.com/bastean/codexgo/commit/ec3245c9caf81562cfb8c2ae61aa16ee34b4d5e6)) +- **context:** change exchange to router in broker model ([be19870](https://github.com/bastean/codexgo/commit/be198705206d0f0370b73dc5ed8bb9cf1c3d3572)) +- **context:** change notification system workflow ([f7ec73c](https://github.com/bastean/codexgo/commit/f7ec73cd038337f63b792f1f2623285b7e0eb854)) +- **context:** change package names in shared module ([e26da1e](https://github.com/bastean/codexgo/commit/e26da1e123692559f9a84a2ad3f0e53c4a1b1743)) +- **context:** change time format in errors ([23362e4](https://github.com/bastean/codexgo/commit/23362e40d7ea9fafe2b83a120f3bbc6652644057)) +- **context:** change type name of shared errors ([61c9b93](https://github.com/bastean/codexgo/commit/61c9b93fcec544e6d95c4e3ecd76a0f49ba38e43)) +- **context:** rename folders using plural names instead of the prefix s in shared module ([fe5aabf](https://github.com/bastean/codexgo/commit/fe5aabf7b3815b60a48382076f7f453e065f15f2)) +- **context:** rename packages using plural names in shared module ([db66e1d](https://github.com/bastean/codexgo/commit/db66e1d7e9c62ed8600f048512c37ec30bcba4ef)) +- **makefile:** add MAKE variable to rules with a recursive recipe ([75e31a8](https://github.com/bastean/codexgo/commit/75e31a84e9b58b1723270bce738162240628e21d)) +- **makefile:** change target names of test rules ([f078581](https://github.com/bastean/codexgo/commit/f0785811deabe3ab8343185cb14ca97933c6341c)) +- rename files using flatcase ([28d3e5f](https://github.com/bastean/codexgo/commit/28d3e5fafad4cdbeed23a8c6bb51724e6e6f746e)) +- **scripts:** change commit message on upgrade script ([9b257a2](https://github.com/bastean/codexgo/commit/9b257a223f1c2cc22dfe9c7dc5b1764389f3f8f1)) +- **server:** add ui class in jquery component selectors ([3c1743e](https://github.com/bastean/codexgo/commit/3c1743ed59d0e35fe5b4d2e2e7d9a4e62066a4a5)) +- **server:** change broker service components to individual files ([93d3c29](https://github.com/bastean/codexgo/commit/93d3c29e4669be36e5ed2f196fdb4e1183314a72)) +- **server:** change error handling from panic to wrapped errors ([1e3d766](https://github.com/bastean/codexgo/commit/1e3d766be194b64960e302b83318a9929f104e5c)) +- **server:** change error messages in services ([0f6a21e](https://github.com/bastean/codexgo/commit/0f6a21e98a89347d721f32b611fdacd6b405430d)) +- **server:** decouple service initializations ([61961d2](https://github.com/bastean/codexgo/commit/61961d2327948df5b9611ff75034c28d9f34a859)) + +### Tests + +- **context:** add spaces between definitions in setup test ([4318ea2](https://github.com/bastean/codexgo/commit/4318ea27f6cac20154489c55917b9d639bd54fe0)) +- **context:** change integration tests to check for wrapped errors instead of panic ([6bb93ac](https://github.com/bastean/codexgo/commit/6bb93ac315e0049a2407952406e8349886274b2a)) +- **context:** change time on expected error messages ([56137d6](https://github.com/bastean/codexgo/commit/56137d695a6663e18272db70c1d98dcf97c03140)) +- **context:** change unit tests to check for wrapped errors instead of panic ([971b9de](https://github.com/bastean/codexgo/commit/971b9de188f998f9b6ecdec5435375be46789c9f)) +- **server:** change acceptance tests to work with the new ui ([8df4c59](https://github.com/bastean/codexgo/commit/8df4c59c9c280d7c7a55285fa662b682b2df9469)) + +## [3.0.1](https://github.com/bastean/codexgo/compare/v3.0.0...v3.0.1) (2024-04-08) + +### Bug Fixes + +- **deps:** upgrade dependencies ([bd92cf7](https://github.com/bastean/codexgo/commit/bd92cf74fced77cb9011171e60f15d687ddc94f7)) +- **makefile:** add phony target ([3c33a90](https://github.com/bastean/codexgo/commit/3c33a9005396067e0a2c130d78648a41bc677f73)) +- **makefile:** remove init-ci rule ([3311e14](https://github.com/bastean/codexgo/commit/3311e144798aaff6dfe34df1c2c8aa9751f3ca68)) + +### Refactors + +- **makefile:** change rules order ([95c6170](https://github.com/bastean/codexgo/commit/95c6170e4719d0702efe2ed75197c42cd3103494)) + +## [3.0.0](https://github.com/bastean/codexgo/compare/v2.0.1...v3.0.0) (2024-04-04) + +### Documentation + +- **readme:** add features ([6d36f5d](https://github.com/bastean/codexgo/commit/6d36f5d75dfc6e1e3cf9cb50ca01d6f7ab1a4b7a)) + +### New Features + +- add account confirmation via email ([66f7b6e](https://github.com/bastean/codexgo/commit/66f7b6eda53e2f3ea897603c032e85f51fe6cf83)) +- add event-driven architecture using rabbitmq ([1fd11cb](https://github.com/bastean/codexgo/commit/1fd11cb1b1b9096dc2aafccd2da8982d6d041279)) +- add example env demo file ([c288d3c](https://github.com/bastean/codexgo/commit/c288d3ccdd7e4d348b915f22c9fa0236df7de247)) +- add gracefully close infrastructure connections ([fb91c9a](https://github.com/bastean/codexgo/commit/fb91c9a569d7f2d6be67e983e19bb53ec5cb5191)) + +### Bug Fixes + +- **deps:** upgrade dependencies ([a27389f](https://github.com/bastean/codexgo/commit/a27389f51cfc9b37d6b11dcc1013c2e02be84ea4)) +- remove files generated by templ ([0adc9c6](https://github.com/bastean/codexgo/commit/0adc9c6cd570b672f8f9719ee0f3691b895804fd)) + +### Refactors + +- change env handling in context to app ([f395933](https://github.com/bastean/codexgo/commit/f395933288ba2ad6fbb95eb683faa7195ebad890)) +- change templ components ([e54c18a](https://github.com/bastean/codexgo/commit/e54c18a8421a1f5a62bb7ef0e2e58f90f2b50f4b)) + +### Tests + +- add individual execution of unit, integration and acceptance tests ([4dc646f](https://github.com/bastean/codexgo/commit/4dc646f52794c0ad80803ce95900c0bf402029fd)) +- remove shared value objects ([618ab5c](https://github.com/bastean/codexgo/commit/618ab5c990f2c54ec574380f54778fb64757ea2a)) + +## [2.0.1](https://github.com/bastean/codexgo/compare/v2.0.0...v2.0.1) (2024-03-13) + +### Bug Fixes + +- **deps:** upgrade dependencies ([4e3f621](https://github.com/bastean/codexgo/commit/4e3f621bf8b3833ef2cd4d7bbe877cf5d38a81ac)) + +### Refactors + +- change domain models ([f80911a](https://github.com/bastean/codexgo/commit/f80911acf48a9bfb115d3328a7834babfa123b02)) + +## [2.0.0](https://github.com/bastean/codexgo/compare/v1.5.0...v2.0.0) (2024-03-02) + +### ⚠ BREAKING CHANGES + +- add standard project layout + +### Bug Fixes + +- **deps:** upgrade dependencies ([f11b15f](https://github.com/bastean/codexgo/commit/f11b15f77899ab50ae0ac744dd84346cb71a7760)) + +### Refactors + +- add standard project layout ([307089c](https://github.com/bastean/codexgo/commit/307089c56975716fb6788e6fafd06ffa8b42f620)) + +## [1.5.0](https://github.com/bastean/codexgo/compare/v1.4.0...v1.5.0) (2024-02-18) + +### New Features + +- add script to sync .env\* files ([e7fcc0b](https://github.com/bastean/codexgo/commit/e7fcc0b6355e5abf00a97526e7becb111cdf2dda)) + +### Bug Fixes + +- **deps:** upgrade dependencies ([fecaafa](https://github.com/bastean/codexgo/commit/fecaafa9bf35e6a5fa71ae0468845bd32bef26ea)) + +## [1.4.0](https://github.com/bastean/codexgo/compare/v1.3.1...v1.4.0) (2024-02-15) + +### New Features + +- add commit message types to include in the changelog ([db06cf9](https://github.com/bastean/codexgo/commit/db06cf95d6d637f097a6745d04302b8f272a50a6)) + +### Bug Fixes + +- **deps:** upgrade dependencies ([80c2256](https://github.com/bastean/codexgo/commit/80c22563516b5da15ea07475fbc94c4fcbffd5c6)) + +## [1.3.1](https://github.com/bastean/codexgo/compare/v1.3.0...v1.3.1) (2024-02-14) + +### Bug Fixes + +- **actions:** upgrade go setup action ([da7bc21](https://github.com/bastean/codexgo/commit/da7bc213a052d088efaac6b20c5ec5ad92f4d037)) +- change live reload ([2f97bdb](https://github.com/bastean/codexgo/commit/2f97bdbe0675a747ac4eddcfe99632dcf0803b0f)) + +## [1.3.0](https://github.com/bastean/codexgo/compare/v1.2.0...v1.3.0) (2024-02-06) + +### Features + +- **actions:** add upgrade workflow ([e2d62d4](https://github.com/bastean/codexgo/commit/e2d62d4d76e56e0dfaabe0cbd474ef23ee1e5687)) +- add script to upgrade dependencies ([a7cd088](https://github.com/bastean/codexgo/commit/a7cd088099d336526e00c6187a835f9938e48a55)) +- **backend:** add secure middleware ([370db08](https://github.com/bastean/codexgo/commit/370db087b6df9ce1aaa3fa2e5589abc9756ec9b2)) + +### Bug Fixes + +- **actions:** add commit push to upgrade workflow ([9ea06db](https://github.com/bastean/codexgo/commit/9ea06dbfdb1f2af22f187734d757fe2ad8b0e88a)) +- **deps:** upgrade dependencies ([811345c](https://github.com/bastean/codexgo/commit/811345c603d2b07ed76f83620fc6386ea90d1861)) +- **deps:** upgrade dependencies ([c99be30](https://github.com/bastean/codexgo/commit/c99be30ae77f766ca09dece90a627f657f8458c3)) + +## [1.2.0](https://github.com/bastean/codexgo/compare/v1.1.0...v1.2.0) (2024-01-28) + +### Features + +- **backend:** add rate limiter middleware ([a6c1b2b](https://github.com/bastean/codexgo/commit/a6c1b2b2a484d0b4ac63364b76c1ba18f8c3e4b3)) + +### Bug Fixes + +- **deps:** upgrade modules dependencies ([d9851aa](https://github.com/bastean/codexgo/commit/d9851aaeb9ff510148935043ab446bea52e3dc26)) +- remove go vet from lint-staged ([3869cbf](https://github.com/bastean/codexgo/commit/3869cbf84fb83bc105b16be0fb6f1a03ab830e9f)) + +## [1.1.0](https://github.com/bastean/codexgo/compare/v1.0.0...v1.1.0) (2024-01-22) + +### Features + +- **actions:** add brew setup ([ef7a00d](https://github.com/bastean/codexgo/commit/ef7a00de57e7cf524223f6e4ced5f7bf2ad71e55)) +- add go vet on lint-staged ([8c52de4](https://github.com/bastean/codexgo/commit/8c52de4ace6d34c2174fe8f03c35e84b6a4040a5)) +- add upx to compress binaries ([9d4e926](https://github.com/bastean/codexgo/commit/9d4e926a3b764f6fe2e49009fb69adc127acb7ea)) +- **devcontainer:** add brew to simplify installation of tools ([8c77ed4](https://github.com/bastean/codexgo/commit/8c77ed45692b6303fcb6235b4ddb612d4e175505)) +- **makefile:** add go mod tidy on lint rule ([d203639](https://github.com/bastean/codexgo/commit/d203639765560e1e375b77b9759bed581c2176ab)) + +### Bug Fixes + +- **docker:** add optimization to compose ([0730183](https://github.com/bastean/codexgo/commit/0730183bfbc522f0c5278a733e63b346fbe41044)) + +## [1.0.0](https://github.com/bastean/codexgo/compare/v0.1.1...v1.0.0) (2024-01-17) + +### ⚠ BREAKING CHANGES + +- **readme:** Ready for v1 + +### Features + +- add codexgo logos ([7ff0641](https://github.com/bastean/codexgo/commit/7ff0641a5db2df5f180242e3d05d93c1ba0cfc92)) +- add trufflehog scan on lint-staged ([bdc473c](https://github.com/bastean/codexgo/commit/bdc473c56a446e0268c1ed02222af2a37185c244)) +- **ci:** add tests job to workflow ([c033f54](https://github.com/bastean/codexgo/commit/c033f5429ddbe07f55281a921b706e6820537ffa)) +- **devcontainer:** add cucumber extension ([fabb6d8](https://github.com/bastean/codexgo/commit/fabb6d8e990b87a71f0152cdeb43b6c28f3cd878)) +- **docker:** add production compose ([4963296](https://github.com/bastean/codexgo/commit/49632964e8a238fd676204cd4eb0bff03a959ac7)) + +### Bug Fixes + +- **backend:** add responsive to alerts ([fc1e4c8](https://github.com/bastean/codexgo/commit/fc1e4c80ba071edc7bfaf39a43bae4aaad8f5b1d)) + +### Documentation + +- **readme:** add contributing section ([54e95f6](https://github.com/bastean/codexgo/commit/54e95f65a5deddd73e1021ad520c848a75ca29cc)) + +## [0.1.1](https://github.com/bastean/codexgo/compare/v0.1.0...v0.1.1) (2024-01-07) + +## 0.1.0 (2024-01-07) + +### Features + +- **backend:** add basis to use htmx with tailwindcss ([5f260b5](https://github.com/bastean/codexgo/commit/5f260b5a594c0eaa50324d04f715f614145f7adc)) +- **backend:** add crud endpoints ([08957ba](https://github.com/bastean/codexgo/commit/08957ba38446d9c3d52e75d225f5b77c1541c7f3)) +- **backend:** add development dockerfile ([a8cef51](https://github.com/bastean/codexgo/commit/a8cef51c8f158fac22c71a567441b3efb49abfc8)) +- **backend:** add pwa ([a370906](https://github.com/bastean/codexgo/commit/a3709064ba027b9c2e60cd157de55c95e41802a3)) +- **context|backend:** add authentication to protected endpoints ([b582c11](https://github.com/bastean/codexgo/commit/b582c112f11e9a206fd037a2ecd7ea2bafa252ce)) +- **context|backend:** add password hashing ([8264127](https://github.com/bastean/codexgo/commit/82641276144048800e1a99ac0806f3a1402f98a7)) +- **context:** add basis to run use cases ([fa28f1f](https://github.com/bastean/codexgo/commit/fa28f1f87471e6c84dccdcebd35fc198bb46b96d)) +- **context:** add crud use cases ([d503539](https://github.com/bastean/codexgo/commit/d5035397c7485e2826e4ed4cf594db2f32ef7145)) +- **context:** add mongo repository adapter ([f25a793](https://github.com/bastean/codexgo/commit/f25a7931fab225edebc6e03a4f6f0a124a8ab05d)) +- **devcontainer:** add prettier extension ([d833c1b](https://github.com/bastean/codexgo/commit/d833c1b62defdc6407534662358c89396948e7dc)) + +### Bug Fixes + +- **ci:** upgrade actions ([3054b85](https://github.com/bastean/codexgo/commit/3054b85e405293668d0e2647b584e7cb0f815710)) +- **release:** change manifest path ([05918f0](https://github.com/bastean/codexgo/commit/05918f0961fed086665c3e8572efcee5cdf9a025)) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3c8a03b --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Bastean + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f8fe0be --- /dev/null +++ b/Makefile @@ -0,0 +1,299 @@ +.PHONY: * + +#*------------VARS------------ + +#*______URL______ + +url-server = http://localhost:8080 +url-github = https://github.com/bastean/codexgo + +#*______Go______ + +go-tidy = go mod tidy -e + +#*______Node______ + +npx = npx --no -- +npm-ci = npm ci --legacy-peer-deps + +release-it = ${npx} release-it -V +release-it-dry = ${npx} release-it -V -d --no-git.requireCleanWorkingDir + +#*______Bash______ + +bash = bash -o pipefail -c + +#*______Git______ + +git-reset-hard = git reset --hard HEAD + +#*______Docker______ + +docker-rm-vol = docker volume rm -f +docker-rm-img = docker rmi -f + +compose = cd deployments/ && docker compose +compose-env = ${compose} --env-file + +#*------------RULES------------ + +#*______Upgrades______ + +upgrade-managers: + #? sudo apt update && sudo apt upgrade -y + npm upgrade -g + +upgrade-go: + go get -t -u ./... + +copydeps: + go run ./scripts/copydeps + +upgrade-node: + ${npx} ncu -ws -u + npm i --legacy-peer-deps + $(MAKE) copydeps + +upgrade-reset: + ${git-reset-hard} + ${npm-ci} + +upgrade: + go run ./scripts/upgrade + +#*______Installations______ + +install-scanners: + curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sudo sh -s -- -b /usr/local/bin v3.63.11 + curl -sSfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo sh -s -- -b /usr/local/bin v0.52.2 + go install github.com/google/osv-scanner/cmd/osv-scanner@latest + +install-linters: + go install honnef.co/go/tools/cmd/staticcheck@latest + npm i -g prettier + +install-tools-dev: install-scanners install-linters + go install github.com/air-verse/air@latest + go install github.com/a-h/templ/cmd/templ@latest + +install-tools-test: + go run github.com/playwright-community/playwright-go/cmd/playwright@latest install chromium --with-deps + npm i -g concurrently wait-on + +install-tooling: install-tools-dev install-tools-test + +install-tooling-ci: install-tools-dev + +#*______Downloads______ + +download-dependencies: + go mod download + ${npm-ci} + +#*______Generators______ + +generate-required: + go generate ./... + find . -name "*_templ.go" -type f -delete + templ generate + +#*______Initializations______ + +init: upgrade-managers install-tooling download-dependencies generate-required + +init-ci: upgrade-managers install-tooling-ci download-dependencies generate-required + +genesis: + git init + git add . + $(MAKE) init + ${npx} husky init + git restore . + +#*______Linters/Formatters______ + +lint: generate-required + go mod tidy + gofmt -l -s -w . + ${npx} prettier --ignore-unknown --write . + templ fmt . + +lint-check: + staticcheck ./... + ${npx} prettier --check . + +#*______Scanners______ + +scan-leaks-local: + sudo trufflehog git file://. --only-verified + trivy repo --scanners secret . + +scan-leaks-remote: + sudo trufflehog git ${url-github} --only-verified + trivy repo --scanners secret ${url-github} + +scan-vulns-local: + osv-scanner --call-analysis=all -r . + trivy repo --scanners vuln . + +scan-misconfigs-local: + trivy repo --scanners misconfig . + +scan-leaks: scan-leaks-local scan-leaks-remote + +scan-vulns: scan-vulns-local + +scan-misconfigs: scan-misconfigs-local + +scans: scan-leaks scan-vulns scan-misconfigs + +#*______Tests______ + +test-sut: + air + +test-clean: generate-required + go clean -testcache + cd test/ && mkdir -p report + +test-codegen: + ${npx} playwright codegen ${url-server} + +test-sync: + ${npx} concurrently -s first -k --names 'SUT,TEST' '$(MAKE) test-sut' '${npx} wait-on -l ${url-server}/health && $(TEST_SYNC)' + +test-unit: test-clean + ${bash} 'go test -v -cover ./pkg/context/... -run TestUnit.* |& tee test/report/unit.report.log' + +test-integration: test-clean + ${bash} 'go test -v -cover ./pkg/context/... -run TestIntegration.* |& tee test/report/integration.report.log' + +test-acceptance-sync: + ${bash} 'SUT_URL="${url-server}" go test -v -cover ./internal/app/... -run TestAcceptance.* |& tee test/report/acceptance.report.log' + +test-acceptance: test-clean + TEST_SYNC="$(MAKE) test-acceptance-sync" $(MAKE) test-sync + +tests-sync: + ${bash} 'SUT_URL="${url-server}" go test -v -cover ./... |& tee test/report/report.log' + +tests: test-clean + TEST_SYNC="$(MAKE) tests-sync" $(MAKE) test-sync + +#*______Releases______ + +release: + ${release-it} + +release-alpha: + ${release-it} --preRelease=alpha + +release-beta: + ${release-it} --preRelease=beta + +release-ci: + ${release-it} --ci --no-git.requireCleanWorkingDir $(OPTIONS) + +release-dry: + ${release-it-dry} + +release-dry-version: + ${release-it-dry} --release-version + +release-dry-changelog: + ${release-it-dry} --changelog + +#*______Builds______ + +build: lint + rm -rf build/ + go build -ldflags="-s -w" -o build/codexgo ./cmd/codexgo + +#*______ENV______ + +syncenv-reset: + ${git-reset-hard} + +syncenv: + cd deployments && go run ../scripts/syncenv + +#*______Git______ + +commit: + ${npx} cz + +WARNING-git-forget: + git rm -r --cached . + git add . + +WARNING-git-genesis: + git clean -e .env* -fdx + ${git-reset-hard} + $(MAKE) init + +#*______Docker______ + +docker-usage: + docker system df + +docker-it: + docker exec -it $(ID) bash + +compose-dev-down: + ${compose-env} .env.dev down + ${docker-rm-vol} codexgo-database-mongo-dev + +compose-dev: compose-dev-down + ${compose-env} .env.dev up + +compose-test-down: + ${compose-env} .env.test down + ${docker-rm-vol} codexgo-database-mongo-test + +compose-test-integration: compose-test-down + ${compose-env} .env.test --env-file .env.test.integration up --exit-code-from codexgo + +compose-test-acceptance: compose-test-down + ${compose-env} .env.test --env-file .env.test.acceptance up --exit-code-from codexgo + +compose-tests: compose-test-down + ${compose-env} .env.test up --exit-code-from codexgo + +compose-prod-down: + ${compose-env} .env.prod down + ${docker-rm-img} codexgo + +compose-prod: compose-prod-down + ${compose-env} .env.prod up + +demo-down: + ${compose-env} .env.demo down + +demo: demo-down + ${compose-env} .env.demo up + +compose-down: compose-dev-down compose-test-down compose-prod-down demo-down + +WARNING-docker-prune-soft: + docker system prune + $(MAKE) compose-down + $(MAKE) docker-usage + +WARNING-docker-prune-hard: + docker system prune --volumes -a + $(MAKE) compose-down + $(MAKE) docker-usage + +#*______Devcontainer______ + +devcontainer: + ${bash} 'echo -e "$(USER_PASSWORD)\n$(USER_PASSWORD)" | sudo passwd vscode' + +connect: + ssh -p 2222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o GlobalKnownHostsFile=/dev/null vscode@localhost + +#*______Fixes______ + +fix-dev: upgrade-go install-tools-dev + +fix-test: upgrade-go install-tools-test diff --git a/README.md b/README.md new file mode 100644 index 0000000..f855ea6 --- /dev/null +++ b/README.md @@ -0,0 +1,459 @@ +

+ + + +[![README Logo](assets/readme/logo.png)](https://github.com/bastean/codexgo) + +

+ +
+ +> Example CRUD project applying Hexagonal Architecture, Domain-Driven Design (DDD), Event-Driven Architecture (EDA), Command Query Responsibility Segregation (CQRS), Behavior-Driven Development (BDD), Continuous Integration (CI), and more... in Go. + +
+ +
+ +
+ +[![License MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![Go Report Card](https://goreportcard.com/badge/github.com/bastean/codexgo/v4)](https://goreportcard.com/report/github.com/bastean/codexgo/v4) +[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](https://github.com/commitizen/cz-cli) +[![Release It!](https://img.shields.io/badge/%F0%9F%93%A6%F0%9F%9A%80-release--it-orange.svg)](https://github.com/release-it/release-it) + +
+ +
+ +[![Upgrade workflow](https://github.com/bastean/codexgo/actions/workflows/upgrade.yml/badge.svg)](https://github.com/bastean/codexgo/actions/workflows/upgrade.yml) +[![CI workflow](https://github.com/bastean/codexgo/actions/workflows/ci.yml/badge.svg)](https://github.com/bastean/codexgo/actions/workflows/ci.yml) +[![Release workflow](https://github.com/bastean/codexgo/actions/workflows/release.yml/badge.svg)](https://github.com/bastean/codexgo/actions/workflows/release.yml) + +
+ +
+ +[![Go Reference](https://pkg.go.dev/badge/github.com/bastean/codexgo/v4.svg)](https://pkg.go.dev/github.com/bastean/codexgo/v4) +[![GitHub Releases](https://img.shields.io/github/v/release/bastean/codexgo.svg)](https://github.com/bastean/codexgo/releases) + +
+ +## Showcase + +
+ + + + + + + + + + + +
+ +## Usage + +### Docker (Demo) + +> [!NOTE] +> +> - [System Requirements](#locally) +> - In the Demo version, the link to confirm the account is sent through the Terminal. +> - _"Hi \, please confirm your account through this link: \"_ +> - We can define our own **SMTP** configuration in the [.env.demo](deployments/.env.demo) file by simply modifying the `CODEXGO_SMTP_*` variables, then we will be able to receive the links by mail. + +```bash +make demo +``` + +## Features + +### Project Layout + +- Based on [Standard Go Project Layout](https://github.com/golang-standards/project-layout). + +### Git + +- Hooks managed by [husky](https://github.com/typicode/husky): + - Pre-Push: + - Scanning Repository for leaks using [TruffleHog CLI](https://github.com/trufflesecurity/trufflehog) and [Trivy](https://github.com/aquasecurity/trivy) + - Pre-Commit: [lint-staged](https://github.com/lint-staged/lint-staged) + - Scanning files for leaks using [TruffleHog CLI](https://github.com/trufflesecurity/trufflehog?tab=readme-ov-file#8-scan-individual-files-or-directories) + - Formatting + - Commit-Msg: [commitlint](https://github.com/conventional-changelog/commitlint) + - Check [Conventional Commits](https://www.conventionalcommits.org) rules +- Commit message helper using [Commitizen](https://github.com/commitizen/cz-cli). + - Interactive prompt that allows you to write commits following the [Conventional Commits](https://www.conventionalcommits.org) rules: + ```bash + make commit + ``` + +### Linting/Formatting Tools + +- Go: **staticcheck** and **gofmt**. +- templ: **templ fmt**. +- Gherkin: **Cucumber extension**. +- Others: **Prettier cli/extension**. + +### Scanners + +- [TruffleHog CLI](https://github.com/trufflesecurity/trufflehog): Secrets. +- [Trivy](https://github.com/aquasecurity/trivy): Secrets, Vulnerabilities and Misconfigurations. +- [OSV-Scanner](https://github.com/google/osv-scanner): Vulnerabilities. + +### Testing Packages + +- Random data generator: [Gofakeit](https://github.com/brianvoe/gofakeit). +- Unit/Integration: [Testify](https://github.com/stretchr/testify). +- Acceptance: [Testify](https://github.com/stretchr/testify), [Godog (Cucumber)](https://github.com/cucumber/godog) and [Playwright](https://github.com/playwright-community/playwright-go). + +### Releases + +- Automatically managed by [Release It!](https://github.com/release-it/release-it): + - Before/After Hooks for: + - Linting + - Testing + - Bump version based on [Conventional Commits](https://www.conventionalcommits.org) and [SemVer](https://semver.org/): + - CHANGELOG generator + - Commits and Tags generator + - GitHub Releases + +### GitHub + +- Actions for: + - Setup Languages and Dependencies +- Workflows running: + - Automatically (Triggered by **Push** or **Pull requests**): + - Secrets Scanning ([TruffleHog Action](https://github.com/trufflesecurity/trufflehog?tab=readme-ov-file#octocat-trufflehog-github-action)) + - Linting + - Testing + - Manually (Using the **Actions tab** on GitHub): + - Upgrade Dependencies + - Automate Release +- Issue Templates **(Defaults)**. + +### Devcontainer + +- Multiple Features already pre-configured: + - Go + - Node + - Docker in Docker +- Extensions and their respective settings to work with: + - Go + - templ + - Cucumber + - Gherkin + - Prettier + - Better Comments + - Todo Tree + - cSpell + +### Docker + +- Dockerfile + - **Multi-stage builds**: + - Development + - Testing + - Build + - Production +- Compose + - Switched by ENVs. + +### Message Broker + +- Routing Key based on [AsyncAPI Topic Definition](https://github.com/fmvilas/topic-definition). + +### Security + +- Form validation at the client using [Fomantic - Form Validation](https://fomantic-ui.com/behaviors/form.html). + - On the server, the validations are performed using the **Value Objects** defined in the **Context**. +- Data **authentication** via **JWT** managed by **Session Cookies**. +- Account confirmation via **Mail** or **Terminal**. +- Password hashing using [Bcrypt](https://pkg.go.dev/golang.org/x/crypto/bcrypt). +- Requests **Rate Limiting**. +- Server log files. + +### Scripts + +- [syncenv](scripts/syncenv/syncenv.go) + - Synchronize all **.env\*** files in the directory using an **.env** model. +- [copydeps](scripts/copydeps/copydeps.go) + - Copies the files required by the browser dependencies from the **node_modules** folder and places them inside the **static** folder on the server. +- [upgrade](scripts/upgrade/upgrade.go) + - Perform the following steps to upgrade the project: + - Upgrade Go, Node and Tools. + - Linting and Testing. + - Commit upgrades. +- [run](deployments/run.sh) + - Display the logs and redirect them to a file whose name depends on the time at which the service was run. + - Used in Production Image. + +## Basic Workflow (Domain > (Infrastructure | Application) > Presentation) + +### Bounded Context (App/Business/Department) > Modules (Troubleshooting) > Layers (Domain, Infrastructure & Application) + +- **Domain (Logic Core)** + - Value Objects (Entities) + - Mother Creators + - Unit Tests + - Messages (Event/Command) + - Mother Creators + - Aggregates (Sets of Entities) + - Aggregate Root (Core Set) + - Mother Creators + - Role Interfaces (Ports) + - Repository + - Broker + - Model Interfaces + - Use Cases + - Handlers/Consumers + - Services (Abstract Logic) + - Errors (Management) +- **Infrastructure (Port Adapters)** + - Persistence + - Repository Mocks + - Implementations (Adapters) + - Integration Tests + - Communication + - Broker Mocks + - Implementations (Adapters) + - Integration Tests +- **Application (Orchestration of Domain Logic)** + - Use Cases + - Implementations + - Commands + - Mother Creators + - Queries/Responses + - Mother Creators + - Handlers/Consumers + - Implementations + - Unit Tests + +### App > Server > (Presentation) + +- **Presentation (Consumers of Bounded Context Modules)** + - Services (Mapping) + - Centralize Imports + - Server + - Templates + - Handlers + - Routes + - Features (Gherkin) + - Acceptance Tests + +### Idiomatic + +- **Domain** + - `errors.New*()`, `errors.BubbleUp()` & `errors.Panic()` + - Only in the "Domain" can we throw a `panic()`. +- **Infrastructure** + - `Open()` & `Close()` + - `session` + - `errors.New*()` & `errors.BubbleUp()` +- **Application** + - `Run()`, `Handle()` & `On()` + - `errors.New*()` & `errors.BubbleUp()` +- **Modules** + - `Start()` & `Stop()` + - `errors.BubbleUp()` +- **Services / Apps** + - `Up()` & `Down()` + - `log.[Wrap]()` + - `errors.New*()` & `errors.BubbleUp()` +- **Main** + - `log.Fatal()` & `log.[Wrap]()` + - Only `main()` can use `log.Fatal()`. +- **Logs** + - `[embed]` + - We use `[]` to "embed" external values such as error messages, fields, etc... inside our messages. + +## First Steps + +### Clone + +#### HTTPS + +```bash +git clone https://github.com/bastean/codexgo.git && cd codexgo +``` + +#### SSH + +```bash +git clone git@github.com:bastean/codexgo.git && cd codexgo +``` + +### Initialize + +#### Dev Container (recommended) + +1. System Requirements + + - [Docker](https://docs.docker.com/get-docker) + + - [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) + +2. Start VS Code + + ```bash + code . + ``` + +3. Open Command Palette + + - Ctrl+Shift+P + +4. Run + + ```txt + Dev Containers: Reopen in Container + ``` + +#### Locally + +1. System Requirements + + - [Go](https://go.dev/doc/install) + - [Node](https://nodejs.org/en/download) + - [Make](https://www.gnu.org/software/make) + - [Docker](https://docs.docker.com/get-docker) + +2. Run + + ```bash + make init + ``` + +### ZIP + +> [!NOTE] +> +> - [System Requirements](#locally) +> - We need to change `` and `` with our own values. + +```bash +curl -sSfLO https://github.com/bastean/codexgo/archive/refs/heads/main.zip \ +&& unzip main.zip \ +&& mv codexgo-main \ +&& rm main.zip \ +&& cd \ +&& make genesis \ +&& git commit -m "chore: add codexgo" \ +&& git branch -M main \ +&& git remote add github https://github.com//.git \ +&& git push -u github main \ +&& git status +``` + +### GitHub Repository + +> [!IMPORTANT] +> These settings are necessary to be able to execute the Actions Workflows. + +#### Settings tab + +##### Actions + +- General + + - Workflow permissions + + - [x] Read and write permissions + +##### Secrets and variables + +- Actions + + - New repository secret + + - `BOT_GPG_PRIVATE_KEY` + + ```bash + gpg --armor --export-secret-key [Pub_Key_ID (*-BOT)] + ``` + + - `BOT_GPG_PASSPHRASE` + +### Run + +#### ENVs + +> [!IMPORTANT] +> Before running it, we must initialize the following environment variable files: +> +> - [.env.example](deployments/.env.example) +> - We will have to create a `.env.(dev|test|prod)` for each runtime environment. +> - In the [.env.example.demo](deployments/.env.example.demo) file, we can see the values that can be used. +> +> In case we only want to run the **Integration** or **Acceptance** tests, in addition to having the `.env.test` file, we must have the following files created: +> +> - [.env.example.test.integration](deployments/.env.example.test.integration) +> - Rename the file to `.env.test.integration`. +> - [.env.example.test.acceptance](deployments/.env.example.test.acceptance) +> - Rename the file to `.env.test.acceptance`. + +#### Development + +```bash +make compose-dev +``` + +#### Tests + +##### Unit + +```bash +make test-unit +``` + +##### Integration + +```bash +make compose-test-integration +``` + +##### Acceptance + +```bash +make compose-test-acceptance +``` + +##### Unit/Integration/Acceptance + +```bash +make compose-tests +``` + +#### Production + +```bash +make compose-prod +``` + +## Tech Stack + +#### Base + +- [Go](https://go.dev) +- [Gin](https://gin-gonic.com) +- [templ](https://templ.guide) + - [Fomantic-UI](https://fomantic-ui.com) +- [RabbitMQ](https://www.rabbitmq.com/tutorials/tutorial-one-go) +- [MongoDB](https://www.mongodb.com/docs/drivers/go) + +#### Please see + +- [go.mod](go.mod) +- [package.json](package.json) + +## Contributing + +- Contributions and Feedback are always welcome! + - [Open a new issue](https://github.com/bastean/codexgo/issues/new/choose) + +## License + +- [MIT](LICENSE) diff --git a/assets/readme/desktop-dashboard.png b/assets/readme/desktop-dashboard.png new file mode 100644 index 0000000..d95966c Binary files /dev/null and b/assets/readme/desktop-dashboard.png differ diff --git a/assets/readme/desktop-home.png b/assets/readme/desktop-home.png new file mode 100644 index 0000000..4862636 Binary files /dev/null and b/assets/readme/desktop-home.png differ diff --git a/assets/readme/logo.png b/assets/readme/logo.png new file mode 100644 index 0000000..52ba8b9 Binary files /dev/null and b/assets/readme/logo.png differ diff --git a/assets/readme/mail-confirm-account.png b/assets/readme/mail-confirm-account.png new file mode 100644 index 0000000..f50ab4a Binary files /dev/null and b/assets/readme/mail-confirm-account.png differ diff --git a/assets/readme/mobile-dashboard.png b/assets/readme/mobile-dashboard.png new file mode 100644 index 0000000..7cf862c Binary files /dev/null and b/assets/readme/mobile-dashboard.png differ diff --git a/assets/readme/mobile-home.png b/assets/readme/mobile-home.png new file mode 100644 index 0000000..0992c3b Binary files /dev/null and b/assets/readme/mobile-home.png differ diff --git a/assets/social-preview/social-preview.png b/assets/social-preview/social-preview.png new file mode 100644 index 0000000..0d5f38d Binary files /dev/null and b/assets/social-preview/social-preview.png differ diff --git a/cmd/codexgo/codex.go b/cmd/codexgo/codex.go new file mode 100644 index 0000000..273718d --- /dev/null +++ b/cmd/codexgo/codex.go @@ -0,0 +1,92 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + "os/signal" + "syscall" + "time" + + "github.com/bastean/codexgo/v4/internal/app/server" + "github.com/bastean/codexgo/v4/internal/pkg/service" + "github.com/bastean/codexgo/v4/internal/pkg/service/env" + "github.com/bastean/codexgo/v4/internal/pkg/service/logger/log" +) + +const cli = "codexgo" + +var ( + err error +) + +var ( + Services = "Services" + Apps = "Apps" +) + +func usage() { + fmt.Printf("Usage: %s [OPTIONS]\n\n", cli) + flag.PrintDefaults() +} + +func main() { + flag.StringVar(&env.ServerGinPort, "port", env.ServerGinPort, "Gin Server Port (optional)") + + flag.Usage = usage + + flag.Parse() + + log.Logo() + + log.Starting(Services) + + if err = service.Up(); err != nil { + log.Fatal(err.Error()) + } + + log.Started(Services) + + log.Starting(Apps) + + go func() { + if err := server.Up(); err != nil { + log.Fatal(err.Error()) + } + }() + + log.Started(Apps) + + log.Info("Press Ctrl+C to exit") + + shutdown := make(chan os.Signal, 1) + + signal.Notify(shutdown, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) + + <-shutdown + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + + defer cancel() + + log.Stopping(Apps) + + if err = server.Down(ctx); err != nil { + log.Error(err.Error()) + } + + log.Stopped(Apps) + + log.Stopping(Services) + + if err = service.Down(ctx); err != nil { + log.Error(err.Error()) + } + + log.Stopped(Services) + + <-ctx.Done() + + log.Info("Exiting...") +} diff --git a/deployments/.env.demo b/deployments/.env.demo new file mode 100644 index 0000000..495440d --- /dev/null +++ b/deployments/.env.demo @@ -0,0 +1,40 @@ +BROKER_RABBITMQ_CONTAINER_NAME=codexgo-broker-rabbit-demo +BROKER_RABBITMQ_CONTAINER_RESTART=on-failure +BROKER_RABBITMQ_NAME=codexgo-demo +BROKER_RABBITMQ_AMQP_PORT=5672 +BROKER_RABBITMQ_ADMIN_PORT=15672 +BROKER_RABBITMQ_ADMIN_USERNAME=codexgo-demo +BROKER_RABBITMQ_ADMIN_PASSWORD=codexgo-demo + +DATABASE_MONGODB_CONTAINER_NAME=codexgo-database-mongo-demo +DATABASE_MONGODB_CONTAINER_VOLUME=codexgo-database-mongo-demo +DATABASE_MONGODB_CONTAINER_RESTART=on-failure +DATABASE_MONGODB_NAME=codexgo-demo +DATABASE_MONGODB_PORT=27017 +DATABASE_MONGODB_ROOT_USERNAME=codexgo-demo +DATABASE_MONGODB_ROOT_PASSWORD=codexgo-demo + +CODEXGO_IMAGE_TAG=codexgo-demo +CODEXGO_IMAGE_BUILD_TARGET=prod + +CODEXGO_CONTAINER_NAME=codexgo-demo +CODEXGO_CONTAINER_VOLUME=codexgo-logs:/app/logs +CODEXGO_CONTAINER_START=./run +CODEXGO_CONTAINER_RESTART=on-failure + +CODEXGO_SMTP_HOST= +CODEXGO_SMTP_PORT= +CODEXGO_SMTP_USERNAME= +CODEXGO_SMTP_PASSWORD= + +CODEXGO_JWT_SECRET_KEY=codexgo-demo + +CODEXGO_SERVER_GIN_HOSTNAME=localhost +CODEXGO_SERVER_GIN_PORT=8080 +CODEXGO_SERVER_GIN_URL=http://localhost:8080 +CODEXGO_SERVER_GIN_MODE=release +CODEXGO_SERVER_GIN_ALLOWED_HOSTS=localhost:8080 +CODEXGO_SERVER_GIN_COOKIE_SECRET_KEY=codexgo-demo +CODEXGO_SERVER_GIN_COOKIE_SESSION_NAME=codexgo-demo + +CODEXGO_DEV_AIR_PROXY_PORT=8080 diff --git a/deployments/.env.example b/deployments/.env.example new file mode 100644 index 0000000..101f52a --- /dev/null +++ b/deployments/.env.example @@ -0,0 +1,40 @@ +BROKER_RABBITMQ_CONTAINER_NAME= +BROKER_RABBITMQ_CONTAINER_RESTART= +BROKER_RABBITMQ_NAME= +BROKER_RABBITMQ_AMQP_PORT= +BROKER_RABBITMQ_ADMIN_PORT= +BROKER_RABBITMQ_ADMIN_USERNAME= +BROKER_RABBITMQ_ADMIN_PASSWORD= + +DATABASE_MONGODB_CONTAINER_NAME= +DATABASE_MONGODB_CONTAINER_VOLUME= +DATABASE_MONGODB_CONTAINER_RESTART= +DATABASE_MONGODB_NAME= +DATABASE_MONGODB_PORT= +DATABASE_MONGODB_ROOT_USERNAME= +DATABASE_MONGODB_ROOT_PASSWORD= + +CODEXGO_IMAGE_TAG= +CODEXGO_IMAGE_BUILD_TARGET= + +CODEXGO_CONTAINER_NAME= +CODEXGO_CONTAINER_VOLUME= +CODEXGO_CONTAINER_START= +CODEXGO_CONTAINER_RESTART= + +CODEXGO_SMTP_HOST= +CODEXGO_SMTP_PORT= +CODEXGO_SMTP_USERNAME= +CODEXGO_SMTP_PASSWORD= + +CODEXGO_JWT_SECRET_KEY= + +CODEXGO_SERVER_GIN_HOSTNAME= +CODEXGO_SERVER_GIN_PORT= +CODEXGO_SERVER_GIN_URL= +CODEXGO_SERVER_GIN_MODE= +CODEXGO_SERVER_GIN_ALLOWED_HOSTS= +CODEXGO_SERVER_GIN_COOKIE_SECRET_KEY= +CODEXGO_SERVER_GIN_COOKIE_SESSION_NAME= + +CODEXGO_DEV_AIR_PROXY_PORT= diff --git a/deployments/.env.example.demo b/deployments/.env.example.demo new file mode 100644 index 0000000..7a98c8f --- /dev/null +++ b/deployments/.env.example.demo @@ -0,0 +1,40 @@ +BROKER_RABBITMQ_CONTAINER_NAME=codexgo-broker-rabbit-(dev|test|prod|demo) +BROKER_RABBITMQ_CONTAINER_RESTART=(on-failure|always) +BROKER_RABBITMQ_NAME=codexgo-(dev|test|prod|demo) +BROKER_RABBITMQ_AMQP_PORT=5672 +BROKER_RABBITMQ_ADMIN_PORT=15672 +BROKER_RABBITMQ_ADMIN_USERNAME=codexgo-(dev|test|prod|demo) +BROKER_RABBITMQ_ADMIN_PASSWORD=codexgo-(dev|test|prod|demo) + +DATABASE_MONGODB_CONTAINER_NAME=codexgo-database-mongo-(dev|test|prod|demo) +DATABASE_MONGODB_CONTAINER_VOLUME=codexgo-database-mongo-(dev|test|prod|demo) +DATABASE_MONGODB_CONTAINER_RESTART=(on-failure|always) +DATABASE_MONGODB_NAME=codexgo-(dev|test|prod|demo) +DATABASE_MONGODB_PORT=27017 +DATABASE_MONGODB_ROOT_USERNAME=codexgo-(dev|test|prod|demo) +DATABASE_MONGODB_ROOT_PASSWORD=codexgo-(dev|test|prod|demo) + +CODEXGO_IMAGE_TAG=codexgo-(dev|test|prod|demo) +CODEXGO_IMAGE_BUILD_TARGET=(dev|test|prod) + +CODEXGO_CONTAINER_NAME=codexgo-(dev|test|prod|demo) +CODEXGO_CONTAINER_VOLUME=(..:/app|codexgo-logs:/app/logs) +CODEXGO_CONTAINER_START=(air|'make tests'|./codexgo|./run) +CODEXGO_CONTAINER_RESTART=(on-failure|always) + +CODEXGO_SMTP_HOST=smtp.example.com +CODEXGO_SMTP_PORT=(25|465|587|2525) +CODEXGO_SMTP_USERNAME=codexgo-(dev|test|prod|demo) +CODEXGO_SMTP_PASSWORD=codexgo-(dev|test|prod|demo) + +CODEXGO_JWT_SECRET_KEY=codexgo-(dev|test|prod|demo) + +CODEXGO_SERVER_GIN_HOSTNAME=localhost +CODEXGO_SERVER_GIN_PORT=8080 +CODEXGO_SERVER_GIN_URL=http://localhost:8080 +CODEXGO_SERVER_GIN_MODE=(debug|test|release) +CODEXGO_SERVER_GIN_ALLOWED_HOSTS=localhost:8080 +CODEXGO_SERVER_GIN_COOKIE_SECRET_KEY=codexgo-(dev|test|prod|demo) +CODEXGO_SERVER_GIN_COOKIE_SESSION_NAME=codexgo-(dev|test|prod|demo) + +CODEXGO_DEV_AIR_PROXY_PORT=(8090|8080) diff --git a/deployments/.env.example.test.acceptance b/deployments/.env.example.test.acceptance new file mode 100644 index 0000000..bf15584 --- /dev/null +++ b/deployments/.env.example.test.acceptance @@ -0,0 +1 @@ +CODEXGO_CONTAINER_START='make test-acceptance' diff --git a/deployments/.env.example.test.integration b/deployments/.env.example.test.integration new file mode 100644 index 0000000..c305e62 --- /dev/null +++ b/deployments/.env.example.test.integration @@ -0,0 +1 @@ +CODEXGO_CONTAINER_START='make test-integration' diff --git a/deployments/Dockerfile b/deployments/Dockerfile new file mode 100644 index 0000000..6d3d2b7 --- /dev/null +++ b/deployments/Dockerfile @@ -0,0 +1,35 @@ +FROM golang:bookworm AS dev + +WORKDIR /app + +RUN apt update && apt upgrade -y + +RUN apt install -y nodejs npm + +COPY Makefile . + +RUN make install-tools-dev + +FROM dev AS test + +WORKDIR /app + +RUN make install-tools-test + +FROM dev AS build + +WORKDIR /app + +COPY . . + +RUN make build + +FROM golang:bookworm AS prod + +WORKDIR /app + +COPY --from=build app/deployments/run.sh run + +RUN chmod +x run + +COPY --from=build app/build/codexgo . diff --git a/deployments/docker-compose.yml b/deployments/docker-compose.yml new file mode 100644 index 0000000..2f030eb --- /dev/null +++ b/deployments/docker-compose.yml @@ -0,0 +1,85 @@ +services: + broker-rabbitmq: + container_name: ${BROKER_RABBITMQ_CONTAINER_NAME} + image: rabbitmq:3-management + environment: + RABBITMQ_DEFAULT_USER: ${BROKER_RABBITMQ_ADMIN_USERNAME} + RABBITMQ_DEFAULT_PASS: ${BROKER_RABBITMQ_ADMIN_PASSWORD} + ports: + - ${BROKER_RABBITMQ_AMQP_PORT}:${BROKER_RABBITMQ_AMQP_PORT} + - ${BROKER_RABBITMQ_ADMIN_PORT}:${BROKER_RABBITMQ_ADMIN_PORT} + restart: ${BROKER_RABBITMQ_CONTAINER_RESTART} + healthcheck: + test: rabbitmq-diagnostics -q status + interval: 12s + timeout: 12s + retries: 12 + + database-mongodb: + container_name: ${DATABASE_MONGODB_CONTAINER_NAME} + image: mongo:4.4 + environment: + MONGO_INITDB_ROOT_USERNAME: ${DATABASE_MONGODB_ROOT_USERNAME} + MONGO_INITDB_ROOT_PASSWORD: ${DATABASE_MONGODB_ROOT_PASSWORD} + command: mongod --quiet --logpath /dev/null + ports: + - ${DATABASE_MONGODB_PORT}:${DATABASE_MONGODB_PORT} + volumes: + - database-mongodb:/data/db + restart: ${DATABASE_MONGODB_CONTAINER_RESTART} + healthcheck: + test: echo 'db.runCommand({serverStatus:1}).ok' | mongo admin -u ${DATABASE_MONGODB_ROOT_USERNAME} -p ${DATABASE_MONGODB_ROOT_PASSWORD} --quiet | grep 1 + interval: 12s + timeout: 12s + retries: 12 + + codexgo: + container_name: ${CODEXGO_CONTAINER_NAME} + image: ${CODEXGO_IMAGE_TAG} + build: + context: .. + dockerfile: deployments/Dockerfile + target: ${CODEXGO_IMAGE_BUILD_TARGET} + environment: + BROKER_RABBITMQ_URI: "amqp://${BROKER_RABBITMQ_ADMIN_USERNAME}:${BROKER_RABBITMQ_ADMIN_PASSWORD}@broker-rabbitmq:${BROKER_RABBITMQ_AMQP_PORT}" + BROKER_RABBITMQ_NAME: ${BROKER_RABBITMQ_NAME} + DATABASE_MONGODB_URI: "mongodb://${DATABASE_MONGODB_ROOT_USERNAME}:${DATABASE_MONGODB_ROOT_PASSWORD}@database-mongodb:${DATABASE_MONGODB_PORT}" + DATABASE_MONGODB_NAME: ${DATABASE_MONGODB_NAME} + CODEXGO_SMTP_HOST: ${CODEXGO_SMTP_HOST} + CODEXGO_SMTP_PORT: ${CODEXGO_SMTP_PORT} + CODEXGO_SMTP_USERNAME: ${CODEXGO_SMTP_USERNAME} + CODEXGO_SMTP_PASSWORD: ${CODEXGO_SMTP_PASSWORD} + CODEXGO_JWT_SECRET_KEY: ${CODEXGO_JWT_SECRET_KEY} + CODEXGO_SERVER_GIN_HOSTNAME: ${CODEXGO_SERVER_GIN_HOSTNAME} + CODEXGO_SERVER_GIN_PORT: ${CODEXGO_SERVER_GIN_PORT} + CODEXGO_SERVER_GIN_URL: ${CODEXGO_SERVER_GIN_URL} + CODEXGO_SERVER_GIN_MODE: ${CODEXGO_SERVER_GIN_MODE} + CODEXGO_SERVER_GIN_ALLOWED_HOSTS: ${CODEXGO_SERVER_GIN_ALLOWED_HOSTS} + CODEXGO_SERVER_GIN_COOKIE_SECRET_KEY: ${CODEXGO_SERVER_GIN_COOKIE_SECRET_KEY} + CODEXGO_SERVER_GIN_COOKIE_SESSION_NAME: ${CODEXGO_SERVER_GIN_COOKIE_SESSION_NAME} + CODEXGO_DEV_AIR_PROXY_PORT: ${CODEXGO_DEV_AIR_PROXY_PORT} + command: ${CODEXGO_CONTAINER_START} + ports: + - ${CODEXGO_SERVER_GIN_PORT}:${CODEXGO_SERVER_GIN_PORT} + - ${CODEXGO_DEV_AIR_PROXY_PORT}:${CODEXGO_DEV_AIR_PROXY_PORT} + volumes: + - ${CODEXGO_CONTAINER_VOLUME} + - codexgo-modules:/go/pkg/mod + restart: ${CODEXGO_CONTAINER_RESTART} + depends_on: + broker-rabbitmq: + condition: service_healthy + database-mongodb: + condition: service_healthy + +volumes: + database-mongodb: + name: ${DATABASE_MONGODB_CONTAINER_VOLUME} + codexgo-logs: + name: codexgo-logs + codexgo-modules: + name: codexgo-modules + +networks: + default: + name: codexgo diff --git a/deployments/run.sh b/deployments/run.sh new file mode 100644 index 0000000..d497c87 --- /dev/null +++ b/deployments/run.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +logs=logs + +today=$(date -u +%d-%m-%Y) + +mkdir -p $logs/$today + +now=$(date -u +%H_%M_%S) + +log=$logs/$today/$now.log + +./codexgo |& tee $log diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5b53e4b --- /dev/null +++ b/go.mod @@ -0,0 +1,80 @@ +module github.com/bastean/codexgo/v4 + +go 1.22 + +require ( + github.com/JGLTechnologies/gin-rate-limit v1.5.4 + github.com/a-h/templ v0.2.747 + github.com/brianvoe/gofakeit/v7 v7.0.4 + github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be + github.com/cucumber/godog v0.14.1 + github.com/fatih/color v1.17.0 + github.com/gin-contrib/secure v1.1.0 + github.com/gin-contrib/sessions v1.0.1 + github.com/gin-gonic/gin v1.10.0 + github.com/go-playground/validator/v10 v10.22.0 + github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/google/uuid v1.6.0 + github.com/playwright-community/playwright-go v0.4501.1 + github.com/rabbitmq/amqp091-go v1.10.0 + github.com/stretchr/testify v1.9.0 + go.mongodb.org/mongo-driver v1.16.0 + golang.org/x/crypto v0.25.0 + golang.org/x/text v0.16.0 +) + +require ( + github.com/bytedance/sonic v1.12.0 // indirect + github.com/bytedance/sonic/loader v0.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/cucumber/gherkin/go/v26 v26.2.0 // indirect + github.com/cucumber/messages/go/v21 v21.0.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/deckarep/golang-set/v2 v2.6.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/gabriel-vasile/mimetype v1.4.5 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-jose/go-jose/v3 v3.0.3 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-stack/stack v1.8.1 // indirect + github.com/goccy/go-json v0.10.3 // indirect + github.com/gofrs/uuid v4.4.0+incompatible // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/gorilla/context v1.1.2 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect + github.com/gorilla/sessions v1.3.0 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-memdb v1.3.4 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/montanaflynn/stats v0.7.1 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/redis/go-redis/v9 v9.6.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.2 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect + golang.org/x/net v0.27.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.22.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..81e1641 --- /dev/null +++ b/go.sum @@ -0,0 +1,241 @@ +github.com/JGLTechnologies/gin-rate-limit v1.5.4 h1:1hIaXIdGM9MZFZlXgjWJLpxaK0WHEa5MeloK49nmQsc= +github.com/JGLTechnologies/gin-rate-limit v1.5.4/go.mod h1:mGEhNzlHEg/Tk+KH/mKylZLTfDjACnx7MVYaAlj07eU= +github.com/a-h/templ v0.2.747 h1:D0dQ2lxC3W7Dxl6fxQ/1zZHBQslSkTSvl5FxP/CfdKg= +github.com/a-h/templ v0.2.747/go.mod h1:69ObQIbrcuwPCU32ohNaWce3Cb7qM5GMiqN1K+2yop4= +github.com/brianvoe/gofakeit/v7 v7.0.4 h1:Mkxwz9jYg8Ad8NvT9HA27pCMZGFQo08MK6jD0QTKEww= +github.com/brianvoe/gofakeit/v7 v7.0.4/go.mod h1:QXuPeBw164PJCzCUZVmgpgHJ3Llj49jSLVkKPMtxtxA= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bytedance/sonic v1.12.0 h1:YGPgxF9xzaCNvd/ZKdQ28yRovhfMFZQjuk6fKBzZ3ls= +github.com/bytedance/sonic v1.12.0/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM= +github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ= +github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cucumber/gherkin/go/v26 v26.2.0 h1:EgIjePLWiPeslwIWmNQ3XHcypPsWAHoMCz/YEBKP4GI= +github.com/cucumber/gherkin/go/v26 v26.2.0/go.mod h1:t2GAPnB8maCT4lkHL99BDCVNzCh1d7dBhCLt150Nr/0= +github.com/cucumber/godog v0.14.1 h1:HGZhcOyyfaKclHjJ+r/q93iaTJZLKYW6Tv3HkmUE6+M= +github.com/cucumber/godog v0.14.1/go.mod h1:FX3rzIDybWABU4kuIXLZ/qtqEe1Ac5RdXmqvACJOces= +github.com/cucumber/messages/go/v21 v21.0.1 h1:wzA0LxwjlWQYZd32VTlAVDTkW6inOFmSM+RuOwHZiMI= +github.com/cucumber/messages/go/v21 v21.0.1/go.mod h1:zheH/2HS9JLVFukdrsPWoPdmUtmYQAQPLk7w5vWsk5s= +github.com/cucumber/messages/go/v22 v22.0.0/go.mod h1:aZipXTKc0JnjCsXrJnuZpWhtay93k7Rn3Dee7iyPJjs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= +github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= +github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= +github.com/gin-contrib/secure v1.1.0 h1:wy/psCWbgUBDCLH13KgB/m06NHXb1jczSTRp+H2hK7E= +github.com/gin-contrib/secure v1.1.0/go.mod h1:LtEfyy326NRwgkUq8ac6npf845L0L9B8yfEaLcxMHIc= +github.com/gin-contrib/sessions v1.0.1 h1:3hsJyNs7v7N8OtelFmYXFrulAf6zSR7nW/putcPEHxI= +github.com/gin-contrib/sessions v1.0.1/go.mod h1:ouxSFM24/OgIud5MJYQJLpy6AwxQ5EYO9yLhbtObGkM= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= +github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= +github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= +github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.3.1+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= +github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o= +github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.3.0 h1:XYlkq7KcpOB2ZhHBPv5WpjMIxrQosiZanfoy1HLZFzg= +github.com/gorilla/sessions v1.3.0/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= +github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-memdb v1.3.4 h1:XSL3NR682X/cVk2IeV0d70N4DZ9ljI885xAEU8IoK3c= +github.com/hashicorp/go-memdb v1.3.4/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYiNm53bMpgSg= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= +github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/playwright-community/playwright-go v0.4501.1 h1:kz8SIfR6nEI8blk77nTVD0K5/i37QP5rY/o8a1fG+4c= +github.com/playwright-community/playwright-go v0.4501.1/go.mod h1:bpArn5TqNzmP0jroCgw4poSOG9gSeQg490iLqWAaa7w= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw= +github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o= +github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= +github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.mongodb.org/mongo-driver v1.16.0 h1:tpRsfBJMROVHKpdGyc1BBEzzjDUWjItxbVSZ8Ls4BQ4= +go.mongodb.org/mongo-driver v1.16.0/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= diff --git a/internal/app/server/component/layout/index.templ b/internal/app/server/component/layout/index.templ new file mode 100644 index 0000000..d62e46b --- /dev/null +++ b/internal/app/server/component/layout/index.templ @@ -0,0 +1,71 @@ +package layout + +import ( + "github.com/bastean/codexgo/v4/internal/app/server/component/scripts" + "github.com/bastean/codexgo/v4/internal/app/server/component/scripts/fomantic" + "github.com/bastean/codexgo/v4/internal/app/server/component/scripts/jquery" + "github.com/bastean/codexgo/v4/internal/app/server/component/scripts/storage" +) + +templ Index(headScripts scripts.Head, bodyScripts scripts.Body) { + + + + + + + + + + + + + + + codexGO + + + + + + + + + + + + + + @storage.Init() + + @jquery.Init() + + @fomantic.Init() + + for _, headScript := range headScripts { + @headScript + } + + +
+ { children... } +
+ for _, bodyScript := range bodyScripts { + @bodyScript + } + + +} diff --git a/internal/app/server/component/page/dashboard/form.delete.templ b/internal/app/server/component/page/dashboard/form.delete.templ new file mode 100644 index 0000000..81cbd5d --- /dev/null +++ b/internal/app/server/component/page/dashboard/form.delete.templ @@ -0,0 +1,92 @@ +package dashboard + +var DeleteFormTagId = "delete" + +script DeleteFormInit(formTagId string) { + $(`#${formTagId}`) + .form({ + on: "blur", + inline: true, + preventLeaving: true, + keyboardShortcuts: false, + fields: { + Password: { + rules: [ + { + type: "empty", + prompt: "{name} is required to delete the account" + }, + { + type: "size[8..64]" + }, + { + type: "regExp[/^.*[^0-9].*$/]", + prompt: "{name} cannot be only numbers" + } + ] + }, + ConfirmPassword: { + rules: [ + { + type: "match[Password]" + } + ] + } + } + }) + .api({ + action: "delete user", + method: "DELETE", + onSuccess: function(response, element, xhr) { + $.toast({ + class: "success", + message: response.Message, + showProgress: "top", + }); + + _.delay(function() { + Storage.Clear(); + window.location.replace("/"); + }, 1000); + }, + onFailure: function(response, element, xhr) { + $.toast({ + class: "error", + message: response.Message, + showProgress: "top" + }); + } + }) + ; +} + +script DeleteFormShow() { + $(".ui.mini.modal").modal("show"); +} + +templ DeleteForm() { + + @DeleteFormInit(DeleteFormTagId) +} diff --git a/internal/app/server/component/page/dashboard/form.update.templ b/internal/app/server/component/page/dashboard/form.update.templ new file mode 100644 index 0000000..374322c --- /dev/null +++ b/internal/app/server/component/page/dashboard/form.update.templ @@ -0,0 +1,158 @@ +package dashboard + +var UpdateFormTagId = "update" + +script UpdateFormInit(formTagId string) { + $(`#${formTagId}`) + .form({ + on: "blur", + inline: true, + preventLeaving: true, + keyboardShortcuts: false, + fields: { + Email: { + optional: true, + rules: [ + { + type: "email" + } + ] + }, + Username: { + optional: true, + rules: [ + { + type: "size[2..20]" + }, + { + type: "regExp[/^[A-Za-z0-9]+$/]", + prompt: "{name} must be alphanumeric only" + }, + { + type: "regExp[/^.*[^0-9].*$/]", + prompt: "{name} cannot be only numbers" + } + ] + }, + UpdatedPassword: { + optional: true, + rules: [ + { + type: "size[8..64]" + }, + { + type: "regExp[/^.*[^0-9].*$/]", + prompt: "{name} cannot be only numbers" + } + ] + }, + ConfirmPassword: { + depends: "UpdatedPassword", + rules: [ + { + type: "match[UpdatedPassword]" + } + ] + }, + Password: { + rules: [ + { + type: "empty", + prompt: "{name} is required to update the account settings" + }, + { + type: "size[8..64]" + }, + { + type: "regExp[/^.*[^0-9].*$/]", + prompt: "{name} cannot be only numbers" + } + ] + } + } + }) + .api({ + action: "update user", + method: "PATCH", + onSuccess: function(response, element, xhr) { + $.toast({ + class: "success", + message: response.Message, + showProgress: "top", + }); + + _.delay(function() { + window.location.replace("/dashboard"); + }, 1000); + }, + onFailure: function(response, element, xhr) { + $.toast({ + class: "error", + message: response.Message, + showProgress: "top" + }); + } + }) + ; +} + +templ UpdateForm(email, username string) { +
+

+ Account settings +

+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+ +
+ + +
+
+
+
+
+
+ +
+
+ +
+
+
+
+ @UpdateFormInit(UpdateFormTagId) +} diff --git a/internal/app/server/component/page/dashboard/page.dashboard.templ b/internal/app/server/component/page/dashboard/page.dashboard.templ new file mode 100644 index 0000000..b5efec8 --- /dev/null +++ b/internal/app/server/component/page/dashboard/page.dashboard.templ @@ -0,0 +1,112 @@ +package dashboard + +import ( + "github.com/bastean/codexgo/v4/internal/app/server/component/layout" + "github.com/bastean/codexgo/v4/internal/app/server/component/scripts" + "github.com/bastean/codexgo/v4/internal/pkg/service/user" +) + +script PageInit() { + $(".ui.container") + .transition("fade in", "3s") + ; + + $(".ui.menu .right .dropdown") + .dropdown() + ; + + $(".ui.menu .right .header .icon") + .popup() + ; + + $(".ui.blue.nag") + .nag({ + key: "account-confirmation", + value: true + }) + ; + + $(".ui.green.nag") + .nag({ + key: "account-confirmed", + value: true + }) + ; +} + +script Logout() { + Storage.ClearSession(); + window.location.replace("/"); +} + +templ Page(user *user.ReadResponse) { + @layout.Index(scripts.Head{}, scripts.Body{}) { +
+
+ +
+
+ @UpdateForm(user.Email, user.Username) + @DeleteForm() +
+ if !user.Verified { +
+
+ + Account Confirmation +
+
Link sent. Please check your inbox
+ +
+ } else { +
+
+ + Account Confirmed +
+ +
+ } +
+ @PageInit() + } +} diff --git a/internal/app/server/component/page/home/form.login.templ b/internal/app/server/component/page/home/form.login.templ new file mode 100644 index 0000000..1394c17 --- /dev/null +++ b/internal/app/server/component/page/home/form.login.templ @@ -0,0 +1,82 @@ +package home + +var LoginFormTagId = "login" + +script LoginFormInit(formTagId string) { + $(`#${formTagId}`) + .form({ + on: "blur", + inline: true, + preventLeaving: true, + keyboardShortcuts: false, + fields: { + Email: { + rules: [ + { + type: "email" + } + ] + }, + Password: { + rules: [ + { + type: "size[8..64]" + }, + { + type: "regExp[/^.*[^0-9].*$/]", + prompt: "{name} cannot be only numbers" + } + ] + } + } + }) + .api({ + action: "login user", + method: "POST", + onSuccess: function(response, element, xhr) { + $.toast({ + class: "success", + message: response.Message, + showProgress: "top", + }); + + _.delay(function() { + window.location.replace("/dashboard"); + }, 1000); + }, + onFailure: function(response, element, xhr) { + $.toast({ + class: "error", + message: response.Message, + showProgress: "top" + }); + } + }) + ; +} + +templ LoginForm() { +
+

+ Sign in to your account +
Don't have an account? Sign up
+

+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ @LoginFormInit(LoginFormTagId) +} diff --git a/internal/app/server/component/page/home/form.register.templ b/internal/app/server/component/page/home/form.register.templ new file mode 100644 index 0000000..932be7f --- /dev/null +++ b/internal/app/server/component/page/home/form.register.templ @@ -0,0 +1,148 @@ +package home + +var RegisterFormTagId = "register" + +script RegisterFormInit(formTagId, loginTabTagId string) { + $(`#${formTagId}`) + .form({ + on: "blur", + inline: true, + preventLeaving: true, + keyboardShortcuts: false, + fields: { + Email: { + rules: [ + { + type: "email" + } + ] + }, + Username: { + rules: [ + { + type: "size[2..20]" + }, + { + type: "regExp[/^[A-Za-z0-9]+$/]", + prompt: "{name} must be alphanumeric only" + }, + { + type: "regExp[/^.*[^0-9].*$/]", + prompt: "{name} cannot be only numbers" + } + ] + }, + Password: { + rules: [ + { + type: "size[8..64]" + }, + { + type: "regExp[/^.*[^0-9].*$/]", + prompt: "{name} cannot be only numbers" + } + ] + }, + ConfirmPassword: { + rules: [ + { + type: "match[Password]" + } + ] + }, + Terms: { + rules: [ + { + type: "checked", + prompt: "Terms & Conditions must be checked" + } + ] + } + } + }) + .api({ + action: "create user", + method: "PUT", + beforeSend: function(settings) { + settings.data.Id = crypto.randomUUID(); + + settings.data = JSON.stringify(settings.data); + + return settings; + }, + onSuccess: function(response, element, xhr) { + $.toast({ + class: "success", + message: response.Message, + showProgress: "top", + }); + + _.delay(function() { + $.tab("change tab", loginTabTagId); + $(`#${formTagId}`).form("reset"); + }, 1000); + }, + onFailure: function(response, element, xhr) { + $.toast({ + class: "error", + message: response.Message, + showProgress: "top" + }); + } + }) + ; +} + +script ShowTerms(modalTagId string) { + $(`#${modalTagId}`).modal("show"); +} + +templ RegisterForm() { +
+

+ Create an account +
Already have an account? Sign in
+

+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+
+
+
+ + +
+
+ +
+
+
+ @RegisterFormInit(RegisterFormTagId, LoginTabTagId) +} diff --git a/internal/app/server/component/page/home/modal.terms.templ b/internal/app/server/component/page/home/modal.terms.templ new file mode 100644 index 0000000..c2a9a6a --- /dev/null +++ b/internal/app/server/component/page/home/modal.terms.templ @@ -0,0 +1,28 @@ +package home + +var TermsModalTagId = "terms" + +templ TermsModal() { + +} diff --git a/internal/app/server/component/page/home/page.home.templ b/internal/app/server/component/page/home/page.home.templ new file mode 100644 index 0000000..8f97eea --- /dev/null +++ b/internal/app/server/component/page/home/page.home.templ @@ -0,0 +1,73 @@ +package home + +import ( + "github.com/bastean/codexgo/v4/internal/app/server/component/layout" + "github.com/bastean/codexgo/v4/internal/app/server/component/scripts" +) + +var ( + RegisterTabTagId = "tab-register" + LoginTabTagId = "tab-login" +) + +script PageInit() { + $(".ui.container") + .transition("fade in", "3s") + ; + + $(".ui.container .column .menu .right .item") + .tab({ + context: ".container" + }) + ; + + $(".ui.cookie.nag") + .nag({ + key: "accepts-cookies", + value: true + }) + ; +} + +script ShowTab(tabTagId string) { + $(`.ui.menu .right .item[data-tab=${tabTagId}]`).trigger("click"); +} + +templ Page() { + @layout.Index(scripts.Head{}, scripts.Body{}) { +
+
+ +
+
+
+ @RegisterForm() + @TermsModal() +
+
+ @LoginForm() +
+
+ +
+ @PageInit() + } +} diff --git a/internal/app/server/component/scripts/fomantic/fomantic.templ b/internal/app/server/component/scripts/fomantic/fomantic.templ new file mode 100644 index 0000000..eb85d4c --- /dev/null +++ b/internal/app/server/component/scripts/fomantic/fomantic.templ @@ -0,0 +1,30 @@ +package fomantic + +script Init() { + $.api.settings.api = { + "create user" : "/v4/account", + "login user" : "/v4/account", + "update user" : "/v4/account", + "delete user" : "/v4/account", + }; + + $.api.settings.serializeForm = true; + + $.api.settings.contentType = "application/json; charset=UTF-8"; + + $.api.settings.beforeSend = function(settings) { + settings.data = JSON.stringify(settings.data); + return settings; + }; + + $.api.settings.successTest = function(response) { + if(response && response.Success) { + return response.Success; + } + + return false; + }; +} + +templ Fomantic() { +} diff --git a/internal/app/server/component/scripts/jquery/jquery.templ b/internal/app/server/component/scripts/jquery/jquery.templ new file mode 100644 index 0000000..932815d --- /dev/null +++ b/internal/app/server/component/scripts/jquery/jquery.templ @@ -0,0 +1,9 @@ +package jquery + +script Init() { + //? $(document).ready(handler) + $(() => {}); +} + +templ JQuery() { +} diff --git a/internal/app/server/component/scripts/scripts.templ b/internal/app/server/component/scripts/scripts.templ new file mode 100644 index 0000000..d92e08b --- /dev/null +++ b/internal/app/server/component/scripts/scripts.templ @@ -0,0 +1,9 @@ +package scripts + +type ( + Head = []templ.ComponentScript + Body = []templ.ComponentScript +) + +templ Scripts() { +} diff --git a/internal/app/server/component/scripts/storage/storage.templ b/internal/app/server/component/scripts/storage/storage.templ new file mode 100644 index 0000000..51bdf18 --- /dev/null +++ b/internal/app/server/component/scripts/storage/storage.templ @@ -0,0 +1,60 @@ +package storage + +script Init() { + const Storage = { + MasterKey: "codexgo", + Key: {}, + Init() { + let storage = localStorage.getItem(this.MasterKey); + + if (storage == null) { + localStorage.setItem(this.MasterKey, JSON.stringify({})); + } + }, + Put(key, value) { + let storage = localStorage.getItem(this.MasterKey); + + storage = JSON.parse(storage) + + storage[key] = value; + + localStorage.setItem(this.MasterKey, JSON.stringify(storage)); + }, + Get(key) { + let storage = localStorage.getItem(this.MasterKey); + + storage = JSON.parse(storage) + + return _.get(storage, key, null); + }, + Delete(key) { + let storage = localStorage.getItem(this.MasterKey); + + storage = JSON.parse(storage) + + delete storage[key]; + + localStorage.setItem(this.MasterKey, JSON.stringify(storage)); + }, + async ClearSession() { + localStorage.removeItem(this.MasterKey); + cookieStore.delete(this.MasterKey); + }, + async Clear() { + localStorage.clear(); + + let cookies = await cookieStore.getAll(); + + _.each(cookies, function(cookie) { + cookieStore.delete(cookie); + }); + } + } + + Storage.Init(); + + window.Storage = Storage +} + +templ Storage() { +} diff --git a/internal/app/server/features/page/default.feature b/internal/app/server/features/page/default.feature new file mode 100644 index 0000000..64b2799 --- /dev/null +++ b/internal/app/server/features/page/default.feature @@ -0,0 +1,6 @@ +Feature: Default Redirect + + Scenario: Check the correct redirect for not found page + Given I am on /non-existing page + Then redirect me to / page + And the page title should be codexGO diff --git a/internal/app/server/features/user/create.feature b/internal/app/server/features/user/create.feature new file mode 100644 index 0000000..9da708c --- /dev/null +++ b/internal/app/server/features/user/create.feature @@ -0,0 +1,21 @@ +Feature: Create a new user account + + Scenario: Create a valid non existing account + Given I am on / page + Then I fill the Email with create@example.com + * I fill the Username with create + * I fill the Password with create@example + * I fill the Confirm Password with create@example + * I check the I agree to the terms and conditions + * I click the Sign up button + And I see Account created notification + + Scenario: Create already existing account + Given I am on / page + Then I fill the Email with create@example.com + * I fill the Username with create + * I fill the Password with create@example + * I fill the Confirm Password with create@example + * I check the I agree to the terms and conditions + * I click the Sign up button + But I see Email already registered notification diff --git a/internal/app/server/features/user/delete.feature b/internal/app/server/features/user/delete.feature new file mode 100644 index 0000000..3de1f98 --- /dev/null +++ b/internal/app/server/features/user/delete.feature @@ -0,0 +1,30 @@ +Feature: Delete a user account + + Scenario: Create a valid non existing account + Given I am on / page + Then I fill the Email with delete@example.com + * I fill the Username with delete + * I fill the Password with delete@example + * I fill the Confirm Password with delete@example + * I check the I agree to the terms and conditions + * I click the Sign up button + And I see Account created notification + + Scenario: Login a valid existing account + Given I am on / page + Then I click on the Sign in button + * I fill the Email with delete@example.com + * I fill the Password with delete@example + * I click the Sign in button + * I see Logged in notification + And redirect me to /dashboard page + + Scenario: Delete a valid existing account + Given I am on /dashboard page + Then I open the account menu + * I click on the Delete button + * I fill the Password with delete@example + * I fill the Confirm Password with delete@example + * I click the Approve button + * I see Account deleted notification + And redirect me to / page diff --git a/internal/app/server/features/user/login.feature b/internal/app/server/features/user/login.feature new file mode 100644 index 0000000..5873a56 --- /dev/null +++ b/internal/app/server/features/user/login.feature @@ -0,0 +1,28 @@ +Feature: Login a user account + + Scenario: Create a valid non existing account + Given I am on / page + Then I fill the Email with login@example.com + * I fill the Username with login + * I fill the Password with login@example + * I fill the Confirm Password with login@example + * I check the I agree to the terms and conditions + * I click the Sign up button + And I see Account created notification + + Scenario: Login a valid existing account + Given I am on / page + Then I click on the Sign in button + * I fill the Email with login@example.com + * I fill the Password with login@example + * I click the Sign in button + * I see Logged in notification + And redirect me to /dashboard page + + Scenario: Login a valid non existing account + Given I am on / page + Then I click on the Sign in button + * I fill the Email with non-existing@example.com + * I fill the Password with non-existing@example + * I click the Sign in button + But I see non-existing@example.com not found notification diff --git a/internal/app/server/features/user/update.feature b/internal/app/server/features/user/update.feature new file mode 100644 index 0000000..a6d200b --- /dev/null +++ b/internal/app/server/features/user/update.feature @@ -0,0 +1,30 @@ +Feature: Update a user account + + Scenario: Create a valid non existing account + Given I am on / page + Then I fill the Email with update@example.com + * I fill the Username with update + * I fill the Password with update@example + * I fill the Confirm Password with update@example + * I check the I agree to the terms and conditions + * I click the Sign up button + And I see Account created notification + + Scenario: Login a valid existing account + Given I am on / page + Then I click on the Sign in button + * I fill the Email with update@example.com + * I fill the Password with update@example + * I click the Sign in button + * I see Logged in notification + And redirect me to /dashboard page + + Scenario: Update a valid existing account + Given I am on /dashboard page + Then I fill the Email with updated@example.com + * I fill the Username with updated + * I fill the New Password with updated@example + * I fill the Confirm Password with updated@example + * I fill the Current Password with update@example + * I click the Update button + And I see Account updated notification diff --git a/internal/app/server/handler/health/check.go b/internal/app/server/handler/health/check.go new file mode 100644 index 0000000..cf06272 --- /dev/null +++ b/internal/app/server/handler/health/check.go @@ -0,0 +1,13 @@ +package health + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +func Check() gin.HandlerFunc { + return func(c *gin.Context) { + c.Status(http.StatusOK) + } +} diff --git a/internal/app/server/handler/page/dashboard.go b/internal/app/server/handler/page/dashboard.go new file mode 100644 index 0000000..28099ba --- /dev/null +++ b/internal/app/server/handler/page/dashboard.go @@ -0,0 +1,38 @@ +package page + +import ( + "github.com/bastean/codexgo/v4/internal/app/server/component/page/dashboard" + "github.com/bastean/codexgo/v4/internal/app/server/util/errs" + "github.com/bastean/codexgo/v4/internal/app/server/util/key" + "github.com/bastean/codexgo/v4/internal/pkg/service/errors" + "github.com/bastean/codexgo/v4/internal/pkg/service/user" + "github.com/gin-gonic/gin" +) + +func Dashboard() gin.HandlerFunc { + return func(c *gin.Context) { + id, exists := c.Get(key.UserId) + + if !exists { + errs.AbortErrWithRedirect(c, errs.MissingKey(key.UserId, "Dashboard"), "/") + return + } + + query := new(user.ReadQuery) + + query.Id = id.(string) + + found, err := user.Read.Handle(query) + + if err != nil { + errs.AbortErrWithRedirect(c, errors.BubbleUp(err, "Dashboard"), "/") + return + } + + err = dashboard.Page(found).Render(c.Request.Context(), c.Writer) + + if err != nil { + errs.AbortErr(c, errs.Render(err, "Dashboard")) + } + } +} diff --git a/internal/app/server/handler/page/default.go b/internal/app/server/handler/page/default.go new file mode 100644 index 0000000..208b242 --- /dev/null +++ b/internal/app/server/handler/page/default.go @@ -0,0 +1,13 @@ +package page + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +func Default() gin.HandlerFunc { + return func(c *gin.Context) { + c.Redirect(http.StatusFound, "/") + } +} diff --git a/internal/app/server/handler/page/home.go b/internal/app/server/handler/page/home.go new file mode 100644 index 0000000..117566d --- /dev/null +++ b/internal/app/server/handler/page/home.go @@ -0,0 +1,15 @@ +package page + +import ( + "github.com/bastean/codexgo/v4/internal/app/server/component/page/home" + "github.com/bastean/codexgo/v4/internal/app/server/util/errs" + "github.com/gin-gonic/gin" +) + +func Home() gin.HandlerFunc { + return func(c *gin.Context) { + if err := home.Page().Render(c.Request.Context(), c.Writer); err != nil { + errs.AbortErr(c, errs.Render(err, "Home")) + } + } +} diff --git a/internal/app/server/handler/user/create.go b/internal/app/server/handler/user/create.go new file mode 100644 index 0000000..1beee55 --- /dev/null +++ b/internal/app/server/handler/user/create.go @@ -0,0 +1,36 @@ +package user + +import ( + "net/http" + + "github.com/bastean/codexgo/v4/internal/app/server/util/errs" + "github.com/bastean/codexgo/v4/internal/app/server/util/reply" + "github.com/bastean/codexgo/v4/internal/pkg/service/errors" + "github.com/bastean/codexgo/v4/internal/pkg/service/user" + "github.com/gin-gonic/gin" +) + +func Create() gin.HandlerFunc { + return func(c *gin.Context) { + command := new(user.CreateCommand) + + err := c.BindJSON(command) + + if err != nil { + errs.AbortErr(c, errs.BindingJSON(err, "Create")) + return + } + + err = user.Create.Handle(command) + + if err != nil { + errs.AbortErr(c, errors.BubbleUp(err, "Create")) + return + } + + c.JSON(http.StatusCreated, &reply.JSON{ + Success: true, + Message: "Account created", + }) + } +} diff --git a/internal/app/server/handler/user/delete.go b/internal/app/server/handler/user/delete.go new file mode 100644 index 0000000..c9d90c0 --- /dev/null +++ b/internal/app/server/handler/user/delete.go @@ -0,0 +1,63 @@ +package user + +import ( + "net/http" + + "github.com/bastean/codexgo/v4/internal/app/server/util/errs" + "github.com/bastean/codexgo/v4/internal/app/server/util/key" + "github.com/bastean/codexgo/v4/internal/app/server/util/reply" + "github.com/bastean/codexgo/v4/internal/pkg/service/errors" + "github.com/bastean/codexgo/v4/internal/pkg/service/user" + "github.com/gin-contrib/sessions" + "github.com/gin-gonic/gin" +) + +func Delete() gin.HandlerFunc { + return func(c *gin.Context) { + id, exists := c.Get(key.UserId) + + if !exists { + errs.AbortErr(c, errs.MissingKey(key.UserId, "Delete")) + return + } + + command := new(user.DeleteCommand) + + err := c.BindJSON(command) + + if err != nil { + errs.AbortErr(c, errs.BindingJSON(err, "Delete")) + return + } + + command.Id = id.(string) + + err = user.Delete.Handle(command) + + if err != nil { + errs.AbortErr(c, errors.BubbleUp(err, "Delete")) + return + } + + session := sessions.Default(c) + + session.Clear() + + session.Options(sessions.Options{ + Path: "/", + MaxAge: -1, + }) + + err = session.Save() + + if err != nil { + errs.AbortErr(c, errs.SessionSave(err, "Delete")) + return + } + + c.JSON(http.StatusOK, &reply.JSON{ + Success: true, + Message: "Account deleted", + }) + } +} diff --git a/internal/app/server/handler/user/login.go b/internal/app/server/handler/user/login.go new file mode 100644 index 0000000..79da191 --- /dev/null +++ b/internal/app/server/handler/user/login.go @@ -0,0 +1,61 @@ +package user + +import ( + "net/http" + "time" + + "github.com/bastean/codexgo/v4/internal/app/server/util/errs" + "github.com/bastean/codexgo/v4/internal/app/server/util/key" + "github.com/bastean/codexgo/v4/internal/app/server/util/reply" + "github.com/bastean/codexgo/v4/internal/pkg/service/authentication/jwt" + "github.com/bastean/codexgo/v4/internal/pkg/service/errors" + "github.com/bastean/codexgo/v4/internal/pkg/service/user" + "github.com/gin-contrib/sessions" + "github.com/gin-gonic/gin" +) + +func Login() gin.HandlerFunc { + return func(c *gin.Context) { + query := new(user.LoginQuery) + + err := c.BindJSON(query) + + if err != nil { + errs.AbortErr(c, errs.BindingJSON(err, "Login")) + return + } + + found, err := user.Login.Handle(query) + + if err != nil { + errs.AbortErr(c, errors.BubbleUp(err, "Login")) + return + } + + token, err := jwt.Generate(jwt.Payload{ + key.Exp: time.Now().Add((24 * time.Hour) * 7).Unix(), + key.UserId: found.Id, + }) + + if err != nil { + errs.AbortErr(c, errors.BubbleUp(err, "Login")) + return + } + + session := sessions.Default(c) + + session.Set(key.Authorization, "Bearer "+token) + + err = session.Save() + + if err != nil { + errs.AbortErr(c, errs.SessionSave(err, "Login")) + return + } + + c.JSON(http.StatusOK, &reply.JSON{ + Success: true, + Message: "Logged in", + }) + } +} diff --git a/internal/app/server/handler/user/update.go b/internal/app/server/handler/user/update.go new file mode 100644 index 0000000..a15770c --- /dev/null +++ b/internal/app/server/handler/user/update.go @@ -0,0 +1,46 @@ +package user + +import ( + "net/http" + + "github.com/bastean/codexgo/v4/internal/app/server/util/errs" + "github.com/bastean/codexgo/v4/internal/app/server/util/key" + "github.com/bastean/codexgo/v4/internal/app/server/util/reply" + "github.com/bastean/codexgo/v4/internal/pkg/service/errors" + "github.com/bastean/codexgo/v4/internal/pkg/service/user" + "github.com/gin-gonic/gin" +) + +func Update() gin.HandlerFunc { + return func(c *gin.Context) { + id, exists := c.Get(key.UserId) + + if !exists { + errs.AbortErr(c, errs.MissingKey(key.UserId, "Update")) + return + } + + command := new(user.UpdateCommand) + + err := c.BindJSON(command) + + if err != nil { + errs.AbortErr(c, errs.BindingJSON(err, "Update")) + return + } + + command.Id = id.(string) + + err = user.Update.Handle(command) + + if err != nil { + errs.AbortErr(c, errors.BubbleUp(err, "Update")) + return + } + + c.JSON(http.StatusOK, &reply.JSON{ + Success: true, + Message: "Account updated", + }) + } +} diff --git a/internal/app/server/handler/user/verify.go b/internal/app/server/handler/user/verify.go new file mode 100644 index 0000000..debcae1 --- /dev/null +++ b/internal/app/server/handler/user/verify.go @@ -0,0 +1,35 @@ +package user + +import ( + "net/http" + + "github.com/bastean/codexgo/v4/internal/app/server/util/errs" + "github.com/bastean/codexgo/v4/internal/app/server/util/key" + "github.com/bastean/codexgo/v4/internal/pkg/service/errors" + "github.com/bastean/codexgo/v4/internal/pkg/service/user" + "github.com/gin-gonic/gin" +) + +func Verify() gin.HandlerFunc { + return func(c *gin.Context) { + id := c.Param(key.Id) + + if id == "" { + errs.AbortErrWithRedirect(c, errs.MissingKey(key.Id, "Verify"), "/") + return + } + + command := new(user.VerifyCommand) + + command.Id = id + + err := user.Verify.Handle(command) + + if err != nil { + errs.AbortErr(c, errors.BubbleUp(err, "Verify")) + return + } + + c.Redirect(http.StatusFound, "/dashboard") + } +} diff --git a/internal/app/server/middleware/authentication.go b/internal/app/server/middleware/authentication.go new file mode 100644 index 0000000..da2fe30 --- /dev/null +++ b/internal/app/server/middleware/authentication.go @@ -0,0 +1,41 @@ +package middleware + +import ( + "strings" + + "github.com/bastean/codexgo/v4/internal/app/server/util/errs" + "github.com/bastean/codexgo/v4/internal/app/server/util/key" + "github.com/bastean/codexgo/v4/internal/pkg/service/authentication/jwt" + "github.com/bastean/codexgo/v4/internal/pkg/service/errors" + "github.com/gin-contrib/sessions" + "github.com/gin-gonic/gin" +) + +func Authentication() gin.HandlerFunc { + return func(c *gin.Context) { + session := sessions.Default(c) + + token := session.Get(key.Authorization) + + if token == nil { + errs.AbortWithRedirect(c, "/") + return + } + + signature := strings.Split(token.(string), " ")[1] + + claims, err := jwt.Validate(signature) + + if err != nil { + errs.AbortErrWithRedirect(c, errors.BubbleUp(err, "Authentication"), "/") + return + } + + if value, exists := claims[key.UserId]; exists { + c.Set(key.UserId, value) + c.Next() + } else { + errs.AbortErrWithRedirect(c, errs.MissingKey(key.UserId, "Authentication"), "/") + } + } +} diff --git a/internal/app/server/middleware/cookie.go b/internal/app/server/middleware/cookie.go new file mode 100644 index 0000000..cc28e5a --- /dev/null +++ b/internal/app/server/middleware/cookie.go @@ -0,0 +1,13 @@ +package middleware + +import ( + "github.com/bastean/codexgo/v4/internal/pkg/service/env" + "github.com/gin-contrib/sessions" + "github.com/gin-contrib/sessions/cookie" + "github.com/gin-gonic/gin" +) + +func CookieSession() gin.HandlerFunc { + store := cookie.NewStore([]byte(env.ServerGinCookieSecretKey)) + return sessions.Sessions(env.ServerGinCookieSessionName, store) +} diff --git a/internal/app/server/middleware/error.go b/internal/app/server/middleware/error.go new file mode 100644 index 0000000..435717c --- /dev/null +++ b/internal/app/server/middleware/error.go @@ -0,0 +1,42 @@ +package middleware + +import ( + "net/http" + + "github.com/bastean/codexgo/v4/internal/app/server/util/reply" + "github.com/bastean/codexgo/v4/internal/pkg/service/errors" + "github.com/bastean/codexgo/v4/internal/pkg/service/logger/log" + "github.com/gin-gonic/gin" +) + +func Error() gin.HandlerFunc { + return func(c *gin.Context) { + c.Next() + + var ( + errInvalidValue *errors.ErrInvalidValue + errAlreadyExist *errors.ErrAlreadyExist + errNotExist *errors.ErrNotExist + errFailure *errors.ErrFailure + errInternal *errors.ErrInternal + ) + + for _, err := range c.Errors { + switch { + case errors.As(err, &errInvalidValue): + c.JSON(http.StatusUnprocessableEntity, &reply.JSON{Message: errInvalidValue.What, Data: errInvalidValue.Why}) + case errors.As(err, &errAlreadyExist): + c.JSON(http.StatusConflict, &reply.JSON{Message: errAlreadyExist.What, Data: errAlreadyExist.Why}) + case errors.As(err, &errNotExist): + c.JSON(http.StatusNotFound, &reply.JSON{Message: errNotExist.What, Data: errNotExist.Why}) + case errors.As(err, &errFailure): + c.JSON(http.StatusBadRequest, &reply.JSON{Message: errFailure.What, Data: errFailure.Why}) + case errors.As(err, &errInternal): + c.JSON(http.StatusInternalServerError, &reply.JSON{Message: "Server error. Try again later."}) + fallthrough + default: + log.Error(err.Error()) + } + } + } +} diff --git a/internal/app/server/middleware/header.go b/internal/app/server/middleware/header.go new file mode 100644 index 0000000..28094cc --- /dev/null +++ b/internal/app/server/middleware/header.go @@ -0,0 +1,22 @@ +package middleware + +import ( + "strings" + + "github.com/bastean/codexgo/v4/internal/pkg/service/env" + "github.com/gin-contrib/secure" + "github.com/gin-gonic/gin" +) + +func Headers() gin.HandlerFunc { + return secure.New(secure.Config{ + AllowedHosts: strings.Split(env.ServerGinAllowedHosts, ", "), + STSSeconds: 315360000, + STSIncludeSubdomains: true, + FrameDeny: true, + ContentTypeNosniff: true, + BrowserXssFilter: true, + IENoOpen: true, + ReferrerPolicy: "strict-origin-when-cross-origin", + }) +} diff --git a/internal/app/server/middleware/limiter.go b/internal/app/server/middleware/limiter.go new file mode 100644 index 0000000..ba42343 --- /dev/null +++ b/internal/app/server/middleware/limiter.go @@ -0,0 +1,31 @@ +package middleware + +import ( + "net/http" + "time" + + ratelimit "github.com/JGLTechnologies/gin-rate-limit" + "github.com/gin-gonic/gin" +) + +func keyFunc(c *gin.Context) string { + return c.ClientIP() +} + +func errorHandler(c *gin.Context, info ratelimit.Info) { + c.Status(http.StatusTooManyRequests) +} + +func RateLimiter() gin.HandlerFunc { + store := ratelimit.InMemoryStore(&ratelimit.InMemoryOptions{ + Rate: time.Second, + Limit: 10, + }) + + limiter := ratelimit.RateLimiter(store, &ratelimit.Options{ + ErrorHandler: errorHandler, + KeyFunc: keyFunc, + }) + + return limiter +} diff --git a/internal/app/server/middleware/recover.go b/internal/app/server/middleware/recover.go new file mode 100644 index 0000000..160384c --- /dev/null +++ b/internal/app/server/middleware/recover.go @@ -0,0 +1,14 @@ +package middleware + +import ( + "net/http" + + "github.com/bastean/codexgo/v4/internal/app/server/util/reply" + "github.com/bastean/codexgo/v4/internal/pkg/service/logger/log" + "github.com/gin-gonic/gin" +) + +func Recover(c *gin.Context, err any) { + log.Error(err.(error).Error()) + c.AbortWithStatusJSON(http.StatusInternalServerError, &reply.JSON{Message: "Server error. Try again later."}) +} diff --git a/internal/app/server/package.json b/internal/app/server/package.json new file mode 100644 index 0000000..6d46429 --- /dev/null +++ b/internal/app/server/package.json @@ -0,0 +1,7 @@ +{ + "devDependencies": { + "fomantic-ui": "2.9.3", + "jquery": "3.7.1", + "lodash": "4.17.21" + } +} diff --git a/internal/app/server/router/api/api.go b/internal/app/server/router/api/api.go new file mode 100644 index 0000000..bc1359c --- /dev/null +++ b/internal/app/server/router/api/api.go @@ -0,0 +1,44 @@ +package api + +import ( + "github.com/bastean/codexgo/v4/internal/app/server/handler/health" + "github.com/bastean/codexgo/v4/internal/app/server/handler/user" + "github.com/bastean/codexgo/v4/internal/app/server/middleware" + "github.com/gin-gonic/gin" +) + +type API struct { + *gin.RouterGroup +} + +func (api *API) Public() { + public := api.Group("/") + + account := public.Group("/account") + + account.PUT("/", user.Create()) + account.POST("/", user.Login()) + + account.GET("/verify/:id", user.Verify()) +} + +func (api *API) Private() { + private := api.Group("/", middleware.Authentication()) + + account := private.Group("/account") + + account.PATCH("/", user.Update()) + account.DELETE("/", user.Delete()) +} + +func Use(router *gin.Engine) { + api := &API{ + RouterGroup: router.Group("/v4"), + } + + router.HEAD("/health", health.Check()) + + api.Public() + + api.Private() +} diff --git a/internal/app/server/router/router.go b/internal/app/server/router/router.go new file mode 100644 index 0000000..c2f17ea --- /dev/null +++ b/internal/app/server/router/router.go @@ -0,0 +1,42 @@ +package router + +import ( + "embed" + "net/http" + + "github.com/bastean/codexgo/v4/internal/app/server/middleware" + "github.com/bastean/codexgo/v4/internal/app/server/router/api" + "github.com/bastean/codexgo/v4/internal/app/server/router/view" + "github.com/bastean/codexgo/v4/internal/pkg/service/env" + "github.com/gin-gonic/gin" +) + +var Router *gin.Engine + +func New(files *embed.FS) *gin.Engine { + gin.SetMode(env.ServerGinMode) + + Router = gin.Default() + + Router.Use(gin.CustomRecovery(middleware.Recover)) + + Router.Use(middleware.Error()) + + Router.Use(middleware.Headers()) + + Router.Use(middleware.RateLimiter()) + + Router.Use(middleware.CookieSession()) + + fs := http.FS(files) + + Router.StaticFS("/public", fs) + + Router.StaticFileFS("/robots.txt", "static/robots.txt", fs) + + api.Use(Router) + + view.Use(Router) + + return Router +} diff --git a/internal/app/server/router/view/view.go b/internal/app/server/router/view/view.go new file mode 100644 index 0000000..69cf4b8 --- /dev/null +++ b/internal/app/server/router/view/view.go @@ -0,0 +1,39 @@ +package view + +import ( + "github.com/bastean/codexgo/v4/internal/app/server/handler/page" + "github.com/bastean/codexgo/v4/internal/app/server/middleware" + "github.com/gin-gonic/gin" +) + +type View struct { + *gin.Engine +} + +func (view *View) Public() { + public := view.Group("/") + + home := public.Group("/") + + home.GET("/", page.Home()) +} + +func (view *View) Private() { + private := view.Group("/", middleware.Authentication()) + + dashboard := private.Group("/dashboard") + + dashboard.GET("/", page.Dashboard()) +} + +func Use(router *gin.Engine) { + view := &View{ + Engine: router, + } + + router.NoRoute(page.Default()) + + view.Public() + + view.Private() +} diff --git a/internal/app/server/server.go b/internal/app/server/server.go new file mode 100644 index 0000000..21f305e --- /dev/null +++ b/internal/app/server/server.go @@ -0,0 +1,64 @@ +package server + +import ( + "context" + "embed" + "fmt" + "net/http" + "strings" + + "github.com/bastean/codexgo/v4/internal/app/server/router" + "github.com/bastean/codexgo/v4/internal/pkg/service/env" + "github.com/bastean/codexgo/v4/internal/pkg/service/errors" + "github.com/bastean/codexgo/v4/internal/pkg/service/logger/log" +) + +var ( + Server = &struct { + Gin string + }{ + Gin: log.Server("Gin"), + } +) + +//go:embed static +var Files embed.FS + +var App *http.Server + +func Up() error { + log.Starting(Server.Gin) + + App = &http.Server{ + Addr: ":" + env.ServerGinPort, + Handler: router.New(&Files), + } + + log.Started(Server.Gin) + + log.Info(fmt.Sprintf("%s listening on %s", Server.Gin, env.ServerGinURL)) + + if proxy, ok := env.HasServerGinProxy(); ok { + log.Info(fmt.Sprintf("%s proxy listening on %s", Server.Gin, strings.Replace(env.ServerGinURL, env.ServerGinPort, proxy, 1))) + } + + if err := App.ListenAndServe(); err != nil { + log.CannotBeStarted(Server.Gin) + return errors.BubbleUp(err, "Up") + } + + return nil +} + +func Down(ctx context.Context) error { + log.Stopping(Server.Gin) + + if err := App.Shutdown(ctx); err != nil { + log.CannotBeStopped(Server.Gin) + return errors.BubbleUp(err, "Down") + } + + log.Stopped(Server.Gin) + + return nil +} diff --git a/internal/app/server/server_test.go b/internal/app/server/server_test.go new file mode 100644 index 0000000..d3121fb --- /dev/null +++ b/internal/app/server/server_test.go @@ -0,0 +1,157 @@ +package server_test + +import ( + "context" + "log" + "os" + "strings" + "testing" + "time" + + "github.com/cucumber/godog" + "github.com/playwright-community/playwright-go" + testify "github.com/stretchr/testify/assert" +) + +var ( + sut = os.Getenv("SUT_URL") +) + +var ( + pw *playwright.Playwright + browser playwright.Browser + browserCtx playwright.BrowserContext + page playwright.Page + element playwright.Locator +) + +var ( + headless = true + timeout float64 = 10000 + exact = true + sleep = 4 * time.Second +) + +var ( + err error + assert *testify.Assertions +) + +func InitializeAssert(t *testing.T) { + assert = testify.New(t) +} + +func InitializePlaywright() { + pw, err = playwright.Run() + + if err != nil { + log.Fatalf("could not start playwright: %s", err) + } + + browser, err = pw.Chromium.Launch(playwright.BrowserTypeLaunchOptions{ + Headless: &headless, + Timeout: &timeout, + }) + + if err != nil { + log.Fatalf("could not launch browser: %s", err) + } + + browserCtx, err = browser.NewContext(playwright.BrowserNewContextOptions{BaseURL: &sut}) + + if err != nil { + log.Fatalf("could not create context: %s", err) + } + + page, err = browserCtx.NewPage() + + if err != nil { + log.Fatalf("could not create page: %s", err) + } +} + +func InitializeScenario(sc *godog.ScenarioContext) { + sc.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) { + //? browserCtx.ClearCookies() + return ctx, nil + }) + + sc.Given(`^I am on (.+) page$`, func(route string) { + _, err = page.Goto(route) + assert.NoError(err) + }) + + sc.Then(`^redirect me to (.+) page$`, func(actual string) { + time.Sleep(sleep) + + if actual == "/" { + actual = "" + } + + expected := page.URL() + + assert.True(strings.Contains(expected, actual)) + }) + + sc.Then(`^the page title should be (.+)$`, func(expected string) { + actual, err := page.Title() + + assert.NoError(err) + + assert.Equal(expected, actual) + }) + + sc.Then(`^I click on the (.+) button$`, func(name string) { + element = page.GetByText(name, playwright.PageGetByTextOptions{Exact: &exact}).First() + assert.NoError(element.Click()) + }) + + sc.Then(`^I open the (.+) menu$`, func(name string) { + element = page.GetByRole("heading").First() + assert.NoError(element.Click()) + }) + + sc.Then(`^I fill the (.+) with (.+)$`, func(placeholder, value string) { + element = page.GetByRole("textbox", playwright.PageGetByRoleOptions{Name: placeholder, Exact: &exact}).Last() + assert.NoError(element.Fill(value)) + }) + + sc.Then(`^I click the (.+) button$`, func(name string) { + element = page.GetByRole("button", playwright.PageGetByRoleOptions{Name: name}) + assert.NoError(element.Click()) + }) + + sc.Then(`^I check the (.+)$`, func(name string) { + element = page.GetByRole("checkbox") + assert.NoError(element.Check()) + }) + + sc.Then(`^I see (.+) notification$`, func(expected string) { + element = page.GetByRole("alert") + + actual, err := element.InnerText() + + assert.NoError(err) + + assert.Equal(expected, actual) + }) +} + +func TestAcceptanceServerFeatures(t *testing.T) { + InitializeAssert(t) + + InitializePlaywright() + + suite := godog.TestSuite{ + ScenarioInitializer: InitializeScenario, + Options: &godog.Options{ + Format: "pretty", + Paths: []string{"features"}, + TestingT: t, + }, + } + + if suite.Run() != 0 { + t.Fatal("non-zero status returned, failed to run feature tests") + } +} diff --git a/internal/app/server/static/assets/apple-touch-icon.png b/internal/app/server/static/assets/apple-touch-icon.png new file mode 100644 index 0000000..26001ca Binary files /dev/null and b/internal/app/server/static/assets/apple-touch-icon.png differ diff --git a/internal/app/server/static/assets/favicon.png b/internal/app/server/static/assets/favicon.png new file mode 100644 index 0000000..26001ca Binary files /dev/null and b/internal/app/server/static/assets/favicon.png differ diff --git a/internal/app/server/static/assets/logo.png b/internal/app/server/static/assets/logo.png new file mode 100644 index 0000000..52ba8b9 Binary files /dev/null and b/internal/app/server/static/assets/logo.png differ diff --git a/internal/app/server/static/assets/pwa-any.png b/internal/app/server/static/assets/pwa-any.png new file mode 100644 index 0000000..2fd4f0c Binary files /dev/null and b/internal/app/server/static/assets/pwa-any.png differ diff --git a/internal/app/server/static/assets/pwa-maskable.png b/internal/app/server/static/assets/pwa-maskable.png new file mode 100644 index 0000000..2fd4f0c Binary files /dev/null and b/internal/app/server/static/assets/pwa-maskable.png differ diff --git a/internal/app/server/static/manifest.json b/internal/app/server/static/manifest.json new file mode 100644 index 0000000..7f03c93 --- /dev/null +++ b/internal/app/server/static/manifest.json @@ -0,0 +1,27 @@ +{ + "name": "codexGO", + "short_name": "codexGO", + "version": "4.3.1", + "description": "codexGO", + "author": "Bastean ", + "homepage_url": "https://github.com/bastean/codexgo#readme", + "start_url": ".", + "display": "standalone", + "orientation": "portrait-primary", + "background_color": "#202224", + "theme_color": "#202224", + "icons": [ + { + "src": "assets/pwa-any.png", + "type": "image/png", + "sizes": "512x512", + "purpose": "any" + }, + { + "src": "assets/pwa-maskable.png", + "type": "image/png", + "sizes": "512x512", + "purpose": "maskable" + } + ] +} diff --git a/internal/app/server/static/robots.txt b/internal/app/server/static/robots.txt new file mode 100644 index 0000000..1f53798 --- /dev/null +++ b/internal/app/server/static/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / diff --git a/internal/app/server/util/errs/abort.go b/internal/app/server/util/errs/abort.go new file mode 100644 index 0000000..f04a46e --- /dev/null +++ b/internal/app/server/util/errs/abort.go @@ -0,0 +1,22 @@ +package errs + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +func AbortErr(c *gin.Context, err error) { + c.Error(err) + c.Abort() +} + +func AbortErrWithRedirect(c *gin.Context, err error, route string) { + AbortErr(c, err) + c.Redirect(http.StatusFound, route) +} + +func AbortWithRedirect(c *gin.Context, route string) { + c.Abort() + c.Redirect(http.StatusFound, route) +} diff --git a/internal/app/server/util/errs/binding.go b/internal/app/server/util/errs/binding.go new file mode 100644 index 0000000..ea8264a --- /dev/null +++ b/internal/app/server/util/errs/binding.go @@ -0,0 +1,26 @@ +package errs + +import ( + "encoding/json" + "fmt" + + "github.com/bastean/codexgo/v4/internal/pkg/service/errors" +) + +func BindingJSON(who error, where string) error { + var err *json.UnmarshalTypeError + + if errors.As(who, &err) { + return errors.NewFailure(&errors.Bubble{ + Where: where, + What: fmt.Sprintf("Invalid type field [%s] required type is [%s] and [%s] was obtained", err.Field, err.Type, err.Value), + Who: who, + }) + } + + return errors.NewInternal(&errors.Bubble{ + Where: where, + What: "Cannot bind a JSON to a struct", + Who: who, + }) +} diff --git a/internal/app/server/util/errs/missing.go b/internal/app/server/util/errs/missing.go new file mode 100644 index 0000000..35191f0 --- /dev/null +++ b/internal/app/server/util/errs/missing.go @@ -0,0 +1,14 @@ +package errs + +import ( + "fmt" + + "github.com/bastean/codexgo/v4/internal/pkg/service/errors" +) + +func MissingKey(what, where string) error { + return errors.NewInternal(&errors.Bubble{ + Where: where, + What: fmt.Sprintf("Failure to obtain the value of the key [%s]", what), + }) +} diff --git a/internal/app/server/util/errs/render.go b/internal/app/server/util/errs/render.go new file mode 100644 index 0000000..78a10ae --- /dev/null +++ b/internal/app/server/util/errs/render.go @@ -0,0 +1,13 @@ +package errs + +import ( + "github.com/bastean/codexgo/v4/internal/pkg/service/errors" +) + +func Render(who error, where string) error { + return errors.NewInternal(&errors.Bubble{ + Where: where, + What: "Cannot render a page", + Who: who, + }) +} diff --git a/internal/app/server/util/errs/session.go b/internal/app/server/util/errs/session.go new file mode 100644 index 0000000..733c7db --- /dev/null +++ b/internal/app/server/util/errs/session.go @@ -0,0 +1,13 @@ +package errs + +import ( + "github.com/bastean/codexgo/v4/internal/pkg/service/errors" +) + +func SessionSave(who error, where string) error { + return errors.NewInternal(&errors.Bubble{ + Where: where, + What: "Failure to save session", + Who: who, + }) +} diff --git a/internal/app/server/util/key/key.go b/internal/app/server/util/key/key.go new file mode 100644 index 0000000..2511c77 --- /dev/null +++ b/internal/app/server/util/key/key.go @@ -0,0 +1,8 @@ +package key + +var ( + Authorization = "Authorization" + Exp = "exp" + Id = "id" + UserId = "userId" +) diff --git a/internal/app/server/util/reply/reply.go b/internal/app/server/util/reply/reply.go new file mode 100644 index 0000000..d893eb5 --- /dev/null +++ b/internal/app/server/util/reply/reply.go @@ -0,0 +1,9 @@ +package reply + +type Payload = map[string]any + +type JSON struct { + Success bool + Message string + Data Payload +} diff --git a/internal/pkg/service/authentication/jwt/jwt.go b/internal/pkg/service/authentication/jwt/jwt.go new file mode 100644 index 0000000..be5520d --- /dev/null +++ b/internal/pkg/service/authentication/jwt/jwt.go @@ -0,0 +1,14 @@ +package jwt + +import ( + "github.com/bastean/codexgo/v4/internal/pkg/service/env" + "github.com/bastean/codexgo/v4/pkg/context/shared/infrastructure/authentications/jwt" +) + +type Payload = jwt.Payload + +var ( + JWT = jwt.New(env.JWTSecretKey) + Generate = JWT.Generate + Validate = JWT.Validate +) diff --git a/internal/pkg/service/communication/rabbitmq/binding.go b/internal/pkg/service/communication/rabbitmq/binding.go new file mode 100644 index 0000000..3fb2802 --- /dev/null +++ b/internal/pkg/service/communication/rabbitmq/binding.go @@ -0,0 +1,5 @@ +package rabbitmq + +var ( + BindingEventCreatedSucceeded = "#.event.#.created.succeeded" +) diff --git a/internal/pkg/service/communication/rabbitmq/consumers.go b/internal/pkg/service/communication/rabbitmq/consumers.go new file mode 100644 index 0000000..f4c9877 --- /dev/null +++ b/internal/pkg/service/communication/rabbitmq/consumers.go @@ -0,0 +1,7 @@ +package rabbitmq + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages" +) + +type Consumers = []messages.Consumer diff --git a/internal/pkg/service/communication/rabbitmq/exchange.go b/internal/pkg/service/communication/rabbitmq/exchange.go new file mode 100644 index 0000000..9ba4792 --- /dev/null +++ b/internal/pkg/service/communication/rabbitmq/exchange.go @@ -0,0 +1,11 @@ +package rabbitmq + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages" +) + +func Exchange(name string) *messages.Router { + return &messages.Router{ + Name: name, + } +} diff --git a/internal/pkg/service/communication/rabbitmq/queues.go b/internal/pkg/service/communication/rabbitmq/queues.go new file mode 100644 index 0000000..f5a3a34 --- /dev/null +++ b/internal/pkg/service/communication/rabbitmq/queues.go @@ -0,0 +1,7 @@ +package rabbitmq + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages" +) + +type Queues = []*messages.Queue diff --git a/internal/pkg/service/communication/rabbitmq/rabbitmq.go b/internal/pkg/service/communication/rabbitmq/rabbitmq.go new file mode 100644 index 0000000..c2d7e1d --- /dev/null +++ b/internal/pkg/service/communication/rabbitmq/rabbitmq.go @@ -0,0 +1,44 @@ +package rabbitmq + +import ( + "github.com/bastean/codexgo/v4/internal/pkg/service/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/loggers" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages" + "github.com/bastean/codexgo/v4/pkg/context/shared/infrastructure/communications/rabbitmq" +) + +type RabbitMQ = rabbitmq.RabbitMQ + +var ( + Close = rabbitmq.Close +) + +func Open(uri string, logger loggers.Logger, exchange *messages.Router, queues []*messages.Queue, consumers []messages.Consumer) (*RabbitMQ, error) { + session, err := rabbitmq.Open(uri, logger) + + if err != nil { + return nil, errors.BubbleUp(err, "Open") + } + + if err = session.AddRouter(exchange); err != nil { + return nil, errors.BubbleUp(err, "Open") + } + + for _, queue := range queues { + if err = session.AddQueue(queue); err != nil { + return nil, errors.BubbleUp(err, "Open") + } + + if err = session.AddQueueMessageBind(queue, queue.Bindings); err != nil { + return nil, errors.BubbleUp(err, "Open") + } + } + + for _, consumer := range consumers { + if err = session.AddQueueConsumer(consumer); err != nil { + return nil, errors.BubbleUp(err, "Open") + } + } + + return session, nil +} diff --git a/internal/pkg/service/env/broker.go b/internal/pkg/service/env/broker.go new file mode 100644 index 0000000..be705c2 --- /dev/null +++ b/internal/pkg/service/env/broker.go @@ -0,0 +1,10 @@ +package env + +import ( + "os" +) + +var ( + BrokerRabbitMQURI = os.Getenv("BROKER_RABBITMQ_URI") + BrokerRabbitMQName = os.Getenv("BROKER_RABBITMQ_NAME") +) diff --git a/internal/pkg/service/env/database.go b/internal/pkg/service/env/database.go new file mode 100644 index 0000000..ab2aaae --- /dev/null +++ b/internal/pkg/service/env/database.go @@ -0,0 +1,10 @@ +package env + +import ( + "os" +) + +var ( + DatabaseMongoDBURI = os.Getenv("DATABASE_MONGODB_URI") + DatabaseMongoDBName = os.Getenv("DATABASE_MONGODB_NAME") +) diff --git a/internal/pkg/service/env/jwt.go b/internal/pkg/service/env/jwt.go new file mode 100644 index 0000000..58f5a7c --- /dev/null +++ b/internal/pkg/service/env/jwt.go @@ -0,0 +1,9 @@ +package env + +import ( + "os" +) + +var ( + JWTSecretKey = os.Getenv("CODEXGO_JWT_SECRET_KEY") +) diff --git a/internal/pkg/service/env/server.go b/internal/pkg/service/env/server.go new file mode 100644 index 0000000..fe7dd6a --- /dev/null +++ b/internal/pkg/service/env/server.go @@ -0,0 +1,25 @@ +package env + +import ( + "os" +) + +var ( + ServerGinHostname = os.Getenv("CODEXGO_SERVER_GIN_HOSTNAME") + ServerGinPort = os.Getenv("CODEXGO_SERVER_GIN_PORT") + ServerGinURL = os.Getenv("CODEXGO_SERVER_GIN_URL") + ServerGinMode = os.Getenv("CODEXGO_SERVER_GIN_MODE") + ServerGinAllowedHosts = os.Getenv("CODEXGO_SERVER_GIN_ALLOWED_HOSTS") + ServerGinCookieSecretKey = os.Getenv("CODEXGO_SERVER_GIN_COOKIE_SECRET_KEY") + ServerGinCookieSessionName = os.Getenv("CODEXGO_SERVER_GIN_COOKIE_SESSION_NAME") +) + +func HasServerGinProxy() (string, bool) { + proxy := os.Getenv("CODEXGO_DEV_AIR_PROXY_PORT") + + if proxy != "" && proxy != ServerGinPort { + return proxy, true + } + + return "", false +} diff --git a/internal/pkg/service/env/smtp.go b/internal/pkg/service/env/smtp.go new file mode 100644 index 0000000..77e3f5c --- /dev/null +++ b/internal/pkg/service/env/smtp.go @@ -0,0 +1,12 @@ +package env + +import ( + "os" +) + +var ( + SMTPHost = os.Getenv("CODEXGO_SMTP_HOST") + SMTPPort = os.Getenv("CODEXGO_SMTP_PORT") + SMTPUsername = os.Getenv("CODEXGO_SMTP_USERNAME") + SMTPPassword = os.Getenv("CODEXGO_SMTP_PASSWORD") +) diff --git a/internal/pkg/service/errors/errors.go b/internal/pkg/service/errors/errors.go new file mode 100644 index 0000000..c4bb4d5 --- /dev/null +++ b/internal/pkg/service/errors/errors.go @@ -0,0 +1,28 @@ +package errors + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" +) + +type ( + Bubble = errors.Bubble +) + +type ( + ErrInvalidValue = errors.ErrInvalidValue + ErrAlreadyExist = errors.ErrAlreadyExist + ErrNotExist = errors.ErrNotExist + ErrFailure = errors.ErrFailure + ErrInternal = errors.ErrInternal +) + +var ( + BubbleUp = errors.BubbleUp + As = errors.As + Is = errors.Is +) + +var ( + NewFailure = errors.NewFailure + NewInternal = errors.NewInternal +) diff --git a/internal/pkg/service/logger/log/log.go b/internal/pkg/service/logger/log/log.go new file mode 100644 index 0000000..8efd020 --- /dev/null +++ b/internal/pkg/service/logger/log/log.go @@ -0,0 +1,112 @@ +package log + +import ( + "fmt" + "strings" + + "github.com/bastean/codexgo/v4/pkg/context/shared/infrastructure/records/log" + "github.com/common-nighthawk/go-figure" + "github.com/fatih/color" +) + +var ( + Log = log.New() + Debug = Log.Debug + Error = Log.Error + Fatal = Log.Fatal + Info = Log.Info + Success = Log.Success +) + +var ( + White = color.New(color.FgWhite, color.Bold).Sprint + Cyan = color.New(color.FgCyan, color.Bold).Sprint +) + +func Logo() { + figureCodex := figure.NewFigure("codex", "speed", true).Slicify() + figureGo := figure.NewFigure("GO", "speed", true).Slicify() + + width := 0 + fixedWidth := 0 + + for _, line := range figureCodex { + width = len(line) + + if width > fixedWidth { + fixedWidth = width + } + } + + for i, line := range figureCodex { + width = len(line) + + if width < fixedWidth { + line += strings.Repeat(" ", (fixedWidth - width)) + } + + fmt.Println(White(line), Cyan(figureGo[i])) + } + + fmt.Println() +} + +func Service(service string) string { + return fmt.Sprintf("Service:%s", service) +} + +func Module(module string) string { + return fmt.Sprintf("Module:%s", module) +} + +func Server(app string) string { + return fmt.Sprintf("Server:%s", app) +} + +func Starting(service string) { + Info(fmt.Sprintf("Starting %s...", service)) +} + +func Started(service string) { + Success(fmt.Sprintf("%s started", service)) +} + +func CannotBeStarted(service string) { + Error(fmt.Sprintf("%s cannot be started", service)) +} + +func Stopping(service string) { + Info(fmt.Sprintf("Stopping %s...", service)) +} + +func Stopped(service string) { + Success(fmt.Sprintf("%s stopped", service)) +} + +func CannotBeStopped(service string) { + Error(fmt.Sprintf("%s cannot be stopped", service)) +} + +func EstablishingConnectionWith(service string) { + Info(fmt.Sprintf("Establishing connection with %s...", service)) +} + +func ConnectionEstablishedWith(service string) { + Success(fmt.Sprintf("Connection established with %s", service)) +} + +func ConnectionFailedWith(service string) { + Error(fmt.Sprintf("Connection failed with %s", service)) +} + +func ClosingConnectionWith(service string) { + Info(fmt.Sprintf("Closing connection with %s...", service)) +} + +func ConnectionClosedWith(service string) { + Success(fmt.Sprintf("Connection closed with %s", service)) +} + +func DisconnectionFailedWith(service string) { + Error(fmt.Sprintf("Disconnection failed with %s", service)) +} diff --git a/internal/pkg/service/persistence/mongodb/mongodb.go b/internal/pkg/service/persistence/mongodb/mongodb.go new file mode 100644 index 0000000..5b439de --- /dev/null +++ b/internal/pkg/service/persistence/mongodb/mongodb.go @@ -0,0 +1,12 @@ +package mongodb + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/infrastructure/persistences/mongodb" +) + +type MongoDB = mongodb.MongoDB + +var ( + Open = mongodb.Open + Close = mongodb.Close +) diff --git a/internal/pkg/service/service.go b/internal/pkg/service/service.go new file mode 100644 index 0000000..f61091c --- /dev/null +++ b/internal/pkg/service/service.go @@ -0,0 +1,139 @@ +package service + +import ( + "context" + + "github.com/bastean/codexgo/v4/internal/pkg/service/communication/rabbitmq" + "github.com/bastean/codexgo/v4/internal/pkg/service/env" + "github.com/bastean/codexgo/v4/internal/pkg/service/errors" + "github.com/bastean/codexgo/v4/internal/pkg/service/logger/log" + "github.com/bastean/codexgo/v4/internal/pkg/service/persistence/mongodb" + "github.com/bastean/codexgo/v4/internal/pkg/service/transport/smtp" + "github.com/bastean/codexgo/v4/internal/pkg/service/user" +) + +var ( + Service = &struct { + SMTP, RabbitMQ, MongoDB string + }{ + SMTP: log.Service("SMTP"), + RabbitMQ: log.Service("RabbitMQ"), + MongoDB: log.Service("MongoDB"), + } + Module = &struct { + User string + }{ + User: log.Module("User"), + } +) + +var ( + err error + SMTP *smtp.SMTP + RabbitMQ *rabbitmq.RabbitMQ + MongoDB *mongodb.MongoDB +) + +func Up() error { + log.EstablishingConnectionWith(Service.SMTP) + + user.InitCreated(&user.TerminalConfirmation{ + Logger: log.Log, + ServerURL: env.ServerGinURL, + }, + user.QueueSendConfirmation, + ) + + if env.SMTPHost != "" { + SMTP = smtp.Open( + env.SMTPHost, + env.SMTPPort, + env.SMTPUsername, + env.SMTPPassword, + env.ServerGinURL, + ) + + user.InitCreated(&user.MailConfirmation{SMTP: SMTP}, user.QueueSendConfirmation) + } + + log.ConnectionEstablishedWith(Service.SMTP) + + log.EstablishingConnectionWith(Service.RabbitMQ) + + RabbitMQ, err = rabbitmq.Open( + env.BrokerRabbitMQURI, + log.Log, + rabbitmq.Exchange(env.BrokerRabbitMQName), + rabbitmq.Queues{ + user.QueueSendConfirmation, + }, + rabbitmq.Consumers{ + user.Created, + }, + ) + + if err != nil { + log.ConnectionFailedWith(Service.RabbitMQ) + return errors.BubbleUp(err, "Up") + } + + log.ConnectionEstablishedWith(Service.RabbitMQ) + + log.EstablishingConnectionWith(Service.MongoDB) + + MongoDB, err = mongodb.Open( + env.DatabaseMongoDBURI, + env.DatabaseMongoDBName, + ) + + if err != nil { + log.ConnectionFailedWith(Service.MongoDB) + return errors.BubbleUp(err, "Up") + } + + log.ConnectionEstablishedWith(Service.MongoDB) + + log.Starting(Module.User) + + collection, err := user.OpenCollection( + MongoDB, + "users", + user.Bcrypt, + ) + + if err != nil { + return errors.BubbleUp(err, "Up") + } + + user.Start( + collection, + RabbitMQ, + user.Bcrypt, + ) + + log.Started(Module.User) + + return nil +} + +func Down(ctx context.Context) error { + log.ClosingConnectionWith(Service.RabbitMQ) + + if err = rabbitmq.Close(RabbitMQ); err != nil { + log.DisconnectionFailedWith(Service.RabbitMQ) + return errors.BubbleUp(err, "Down") + } + + log.ConnectionClosedWith(Service.RabbitMQ) + + log.ClosingConnectionWith(Service.MongoDB) + + if err = mongodb.Close(ctx, MongoDB); err != nil { + log.DisconnectionFailedWith(Service.MongoDB) + return errors.BubbleUp(err, "Down") + } + + log.ConnectionClosedWith(Service.MongoDB) + + return nil +} diff --git a/internal/pkg/service/transport/smtp/smtp.go b/internal/pkg/service/transport/smtp/smtp.go new file mode 100644 index 0000000..6b4cadd --- /dev/null +++ b/internal/pkg/service/transport/smtp/smtp.go @@ -0,0 +1,11 @@ +package smtp + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/infrastructure/transports/smtp" +) + +type SMTP = smtp.SMTP + +var ( + Open = smtp.Open +) diff --git a/internal/pkg/service/user/consumer.go b/internal/pkg/service/user/consumer.go new file mode 100644 index 0000000..14434ce --- /dev/null +++ b/internal/pkg/service/user/consumer.go @@ -0,0 +1,20 @@ +package user + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/transfers" + "github.com/bastean/codexgo/v4/pkg/context/user/application/created" +) + +var ( + Created *created.Consumer +) + +func InitCreated(transfer transfers.Transfer, queue *messages.Queue) { + Created = &created.Consumer{ + Created: &created.Created{ + Transfer: transfer, + }, + Queues: []*messages.Queue{queue}, + } +} diff --git a/internal/pkg/service/user/handler.go b/internal/pkg/service/user/handler.go new file mode 100644 index 0000000..3072ad5 --- /dev/null +++ b/internal/pkg/service/user/handler.go @@ -0,0 +1,35 @@ +package user + +import ( + "github.com/bastean/codexgo/v4/pkg/context/user/application/create" + "github.com/bastean/codexgo/v4/pkg/context/user/application/delete" + "github.com/bastean/codexgo/v4/pkg/context/user/application/login" + "github.com/bastean/codexgo/v4/pkg/context/user/application/read" + "github.com/bastean/codexgo/v4/pkg/context/user/application/update" + "github.com/bastean/codexgo/v4/pkg/context/user/application/verify" +) + +type ( + CreateCommand = create.Command + UpdateCommand = update.Command + DeleteCommand = delete.Command + VerifyCommand = verify.Command +) + +type ( + ReadQuery = read.Query + LoginQuery = login.Query +) + +type ( + ReadResponse = read.Response +) + +var ( + Create *create.Handler + Read *read.Handler + Update *update.Handler + Delete *delete.Handler + Verify *verify.Handler + Login *login.Handler +) diff --git a/internal/pkg/service/user/hashing.go b/internal/pkg/service/user/hashing.go new file mode 100644 index 0000000..fbf58ac --- /dev/null +++ b/internal/pkg/service/user/hashing.go @@ -0,0 +1,7 @@ +package user + +import ( + "github.com/bastean/codexgo/v4/pkg/context/user/infrastructure/cryptographic/bcrypt" +) + +var Bcrypt = new(bcrypt.Bcrypt) diff --git a/internal/pkg/service/user/message.go b/internal/pkg/service/user/message.go new file mode 100644 index 0000000..f547d11 --- /dev/null +++ b/internal/pkg/service/user/message.go @@ -0,0 +1,17 @@ +package user + +import ( + "github.com/bastean/codexgo/v4/internal/pkg/service/communication/rabbitmq" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages" +) + +var QueueSendConfirmation = &messages.Queue{ + Name: messages.NewRecipientName(&messages.RecipientNameComponents{ + Service: "user", + Entity: "user", + Action: "send confirmation", + Event: "created", + Status: "succeeded", + }), + Bindings: []string{rabbitmq.BindingEventCreatedSucceeded}, +} diff --git a/internal/pkg/service/user/repository.go b/internal/pkg/service/user/repository.go new file mode 100644 index 0000000..09f0e02 --- /dev/null +++ b/internal/pkg/service/user/repository.go @@ -0,0 +1,9 @@ +package user + +import ( + "github.com/bastean/codexgo/v4/pkg/context/user/infrastructure/persistence/collection" +) + +var ( + OpenCollection = collection.OpenUser +) diff --git a/internal/pkg/service/user/transfer.go b/internal/pkg/service/user/transfer.go new file mode 100644 index 0000000..2283ef0 --- /dev/null +++ b/internal/pkg/service/user/transfer.go @@ -0,0 +1,11 @@ +package user + +import ( + "github.com/bastean/codexgo/v4/pkg/context/user/infrastructure/transport/mail" + "github.com/bastean/codexgo/v4/pkg/context/user/infrastructure/transport/terminal" +) + +type ( + MailConfirmation = mail.Confirmation + TerminalConfirmation = terminal.Confirmation +) diff --git a/internal/pkg/service/user/user.go b/internal/pkg/service/user/user.go new file mode 100644 index 0000000..ea7d72e --- /dev/null +++ b/internal/pkg/service/user/user.go @@ -0,0 +1,55 @@ +package user + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages" + "github.com/bastean/codexgo/v4/pkg/context/user/application/create" + "github.com/bastean/codexgo/v4/pkg/context/user/application/delete" + "github.com/bastean/codexgo/v4/pkg/context/user/application/login" + "github.com/bastean/codexgo/v4/pkg/context/user/application/read" + "github.com/bastean/codexgo/v4/pkg/context/user/application/update" + "github.com/bastean/codexgo/v4/pkg/context/user/application/verify" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/hashing" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/repository" +) + +func Start(repository repository.User, broker messages.Broker, hashing hashing.Hashing) { + Create = &create.Handler{ + Create: &create.Create{ + User: repository, + }, + Broker: broker, + } + + Read = &read.Handler{ + Read: &read.Read{ + User: repository, + }, + } + + Update = &update.Handler{ + Update: &update.Update{ + User: repository, + Hashing: hashing, + }, + } + + Delete = &delete.Handler{ + Delete: &delete.Delete{ + User: repository, + Hashing: hashing, + }, + } + + Verify = &verify.Handler{ + Verify: &verify.Verify{ + User: repository, + }, + } + + Login = &login.Handler{ + Login: &login.Login{ + User: repository, + Hashing: hashing, + }, + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f9df3e9 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,14060 @@ +{ + "name": "codexgo", + "version": "4.6.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "codexgo", + "version": "4.6.1", + "license": "MIT", + "workspaces": [ + "internal/app/*" + ], + "devDependencies": { + "@commitlint/cli": "19.3.0", + "@commitlint/config-conventional": "19.2.2", + "@release-it/bumper": "6.0.1", + "@release-it/conventional-changelog": "8.0.1", + "commitizen": "4.3.0", + "cz-conventional-changelog": "3.3.0", + "husky": "9.1.4", + "lint-staged": "15.2.7", + "npm-check-updates": "17.0.1", + "prettier": "3.3.3", + "release-it": "17.6.0" + }, + "engines": { + "node": ">=20", + "npm": ">=10" + } + }, + "internal/app/server": { + "devDependencies": { + "fomantic-ui": "2.9.3", + "jquery": "3.7.1", + "lodash": "4.17.21" + } + }, + "node_modules/@actions/core": { + "version": "1.10.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@actions/http-client": "^2.0.1", + "uuid": "^8.3.2" + } + }, + "node_modules/@actions/http-client": { + "version": "2.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "tunnel": "^0.0.6", + "undici": "^5.25.4" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.23.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@commitlint/cli": { + "version": "19.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/format": "^19.3.0", + "@commitlint/lint": "^19.2.2", + "@commitlint/load": "^19.2.0", + "@commitlint/read": "^19.2.1", + "@commitlint/types": "^19.0.3", + "execa": "^8.0.1", + "yargs": "^17.0.0" + }, + "bin": { + "commitlint": "cli.js" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/cli/node_modules/execa": { + "version": "8.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@commitlint/cli/node_modules/get-stream": { + "version": "8.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/cli/node_modules/human-signals": { + "version": "5.0.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/@commitlint/cli/node_modules/is-stream": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/cli/node_modules/mimic-fn": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/cli/node_modules/npm-run-path": { + "version": "5.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/cli/node_modules/onetime": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/cli/node_modules/path-key": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/cli/node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@commitlint/cli/node_modules/strip-final-newline": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/config-conventional": { + "version": "19.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.0.3", + "conventional-changelog-conventionalcommits": "^7.0.2" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/config-validator": { + "version": "19.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.0.3", + "ajv": "^8.11.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/ensure": { + "version": "19.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.0.3", + "lodash.camelcase": "^4.3.0", + "lodash.kebabcase": "^4.1.1", + "lodash.snakecase": "^4.1.1", + "lodash.startcase": "^4.4.0", + "lodash.upperfirst": "^4.3.1" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/execute-rule": { + "version": "19.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/format": { + "version": "19.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.0.3", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/format/node_modules/chalk": { + "version": "5.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@commitlint/is-ignored": { + "version": "19.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.0.3", + "semver": "^7.6.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/lint": { + "version": "19.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/is-ignored": "^19.2.2", + "@commitlint/parse": "^19.0.3", + "@commitlint/rules": "^19.0.3", + "@commitlint/types": "^19.0.3" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/load": { + "version": "19.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/config-validator": "^19.0.3", + "@commitlint/execute-rule": "^19.0.0", + "@commitlint/resolve-extends": "^19.1.0", + "@commitlint/types": "^19.0.3", + "chalk": "^5.3.0", + "cosmiconfig": "^9.0.0", + "cosmiconfig-typescript-loader": "^5.0.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "lodash.uniq": "^4.5.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/load/node_modules/chalk": { + "version": "5.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@commitlint/message": { + "version": "19.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/parse": { + "version": "19.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.0.3", + "conventional-changelog-angular": "^7.0.0", + "conventional-commits-parser": "^5.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/read": { + "version": "19.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/top-level": "^19.0.0", + "@commitlint/types": "^19.0.3", + "execa": "^8.0.1", + "git-raw-commits": "^4.0.0", + "minimist": "^1.2.8" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/read/node_modules/execa": { + "version": "8.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@commitlint/read/node_modules/get-stream": { + "version": "8.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/read/node_modules/human-signals": { + "version": "5.0.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/@commitlint/read/node_modules/is-stream": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/read/node_modules/mimic-fn": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/read/node_modules/minimist": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@commitlint/read/node_modules/npm-run-path": { + "version": "5.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/read/node_modules/onetime": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/read/node_modules/path-key": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/read/node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@commitlint/read/node_modules/strip-final-newline": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/resolve-extends": { + "version": "19.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/config-validator": "^19.0.3", + "@commitlint/types": "^19.0.3", + "global-directory": "^4.0.1", + "import-meta-resolve": "^4.0.0", + "lodash.mergewith": "^4.6.2", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/rules": { + "version": "19.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/ensure": "^19.0.3", + "@commitlint/message": "^19.0.0", + "@commitlint/to-lines": "^19.0.0", + "@commitlint/types": "^19.0.3", + "execa": "^8.0.1" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/rules/node_modules/execa": { + "version": "8.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@commitlint/rules/node_modules/get-stream": { + "version": "8.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/rules/node_modules/human-signals": { + "version": "5.0.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/@commitlint/rules/node_modules/is-stream": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/rules/node_modules/mimic-fn": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/rules/node_modules/npm-run-path": { + "version": "5.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/rules/node_modules/onetime": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/rules/node_modules/path-key": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/rules/node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@commitlint/rules/node_modules/strip-final-newline": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/to-lines": { + "version": "19.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/top-level": { + "version": "19.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^7.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/top-level/node_modules/find-up": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^7.2.0", + "path-exists": "^5.0.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/top-level/node_modules/locate-path": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/top-level/node_modules/p-limit": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/top-level/node_modules/p-locate": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/top-level/node_modules/path-exists": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/@commitlint/top-level/node_modules/yocto-queue": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@commitlint/types": { + "version": "19.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/conventional-commits-parser": "^5.0.0", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/types/node_modules/chalk": { + "version": "5.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@hutson/parse-repository-url": { + "version": "5.0.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@iarna/toml": { + "version": "2.2.5", + "dev": true, + "license": "ISC" + }, + "node_modules/@inquirer/figures": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@octokit/auth-token": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^4.0.0", + "@octokit/graphql": "^7.1.0", + "@octokit/request": "^8.3.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/endpoint": { + "version": "9.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request": "^8.3.0", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "22.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "11.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.5.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "13.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.5.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "^5" + } + }, + "node_modules/@octokit/request": { + "version": "8.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^9.0.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/rest": { + "version": "20.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/core": "^5.0.2", + "@octokit/plugin-paginate-rest": "11.3.1", + "@octokit/plugin-request-log": "^4.0.0", + "@octokit/plugin-rest-endpoint-methods": "13.2.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/types": { + "version": "13.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^22.2.0" + } + }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pnpm/config.env-replace": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "4.2.10" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { + "version": "4.2.10", + "dev": true, + "license": "ISC" + }, + "node_modules/@pnpm/npm-conf": { + "version": "2.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@pnpm/config.env-replace": "^1.1.0", + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@release-it/bumper": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@iarna/toml": "^2.2.5", + "detect-indent": "7.0.1", + "fast-glob": "^3.3.2", + "ini": "^4.1.1", + "js-yaml": "^4.1.0", + "lodash-es": "^4.17.21", + "semver": "^7.3.7" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "release-it": "^17.0.0" + } + }, + "node_modules/@release-it/bumper/node_modules/detect-indent": { + "version": "7.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/@release-it/bumper/node_modules/ini": { + "version": "4.1.1", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@release-it/conventional-changelog": { + "version": "8.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "concat-stream": "^2.0.0", + "conventional-changelog": "^5.1.0", + "conventional-recommended-bump": "^9.0.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "release-it": "^17.0.0" + } + }, + "node_modules/@sindresorhus/is": { + "version": "5.6.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/conventional-commits-parser": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/expect": { + "version": "1.20.4", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.11.24", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/vinyl": { + "version": "2.0.12", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/expect": "^1.20.4", + "@types/node": "*" + } + }, + "node_modules/add-stream": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/agent-base": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.12.0", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-colors": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-wrap": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-cyan": { + "version": "0.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-wrap": "0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-gray": { + "version": "0.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-wrap": "0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-red": { + "version": "0.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-wrap": "0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-wrap": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/any-shell-escape": { + "version": "0.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "2.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "node_modules/anymatch/node_modules/braces": { + "version": "2.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/anymatch/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/anymatch/node_modules/fill-range": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/anymatch/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/anymatch/node_modules/is-extendable": { + "version": "0.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/anymatch/node_modules/is-number": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/anymatch/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/anymatch/node_modules/kind-of": { + "version": "6.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/anymatch/node_modules/micromatch": { + "version": "3.1.10", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/anymatch/node_modules/normalize-path": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/anymatch/node_modules/to-regex-range": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/append-buffer": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/archy": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/arr-diff": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-filter": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-map": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-differ": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-each": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-ify": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/array-initial": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "array-slice": "^1.0.0", + "is-number": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-initial/node_modules/is-number": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-last": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-last/node_modules/is-number": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-slice": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-sort": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "default-compare": "^1.0.0", + "get-value": "^2.0.6", + "kind-of": "^5.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-unique": { + "version": "0.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ast-types": { + "version": "0.13.4", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/async": { + "version": "1.5.2", + "dev": true, + "license": "MIT" + }, + "node_modules/async-done": { + "version": "1.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.2", + "process-nextick-args": "^2.0.0", + "stream-exhaust": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/async-each": { + "version": "1.0.6", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/async-retry": { + "version": "1.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "retry": "0.13.1" + } + }, + "node_modules/async-settle": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "async-done": "^1.2.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/atob": { + "version": "2.1.2", + "dev": true, + "license": "(MIT OR Apache-2.0)", + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.19", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001599", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/bach": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-filter": "^1.1.1", + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "array-each": "^1.0.0", + "array-initial": "^1.0.0", + "array-last": "^1.1.1", + "async-done": "^1.2.2", + "async-settle": "^1.0.0", + "now-and-later": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/base": { + "version": "0.11.2", + "dev": true, + "license": "MIT", + "dependencies": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/define-property": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/basic-ftp": { + "version": "5.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/beeper": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/better-console": { + "version": "1.0.1", + "dev": true, + "license": "BSD", + "dependencies": { + "chalk": "^1.1.3", + "cli-table": "~0.3.1" + } + }, + "node_modules/better-console/node_modules/ansi-regex": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/better-console/node_modules/ansi-styles": { + "version": "2.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/better-console/node_modules/chalk": { + "version": "1.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/better-console/node_modules/strip-ansi": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/better-console/node_modules/supports-color": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/binary-extensions": { + "version": "1.13.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/binaryextensions": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/boxen": { + "version": "7.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.1", + "chalk": "^5.2.0", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/ansi-regex": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/boxen/node_modules/ansi-styles": { + "version": "6.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/boxen/node_modules/camelcase": { + "version": "7.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/chalk": { + "version": "5.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/boxen/node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "license": "MIT" + }, + "node_modules/boxen/node_modules/string-width": { + "version": "5.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/boxen/node_modules/type-fest": { + "version": "2.19.0", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/wrap-ansi": { + "version": "8.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.0", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-equal": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cache-base": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cacheable-lookup": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request": { + "version": "10.2.14", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cachedir": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001617", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "2.4.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "dev": true, + "license": "MIT" + }, + "node_modules/chokidar": { + "version": "2.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "optionalDependencies": { + "fsevents": "^1.2.7" + } + }, + "node_modules/chokidar/node_modules/braces": { + "version": "2.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chokidar/node_modules/extend-shallow": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chokidar/node_modules/fill-range": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "3.1.0", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/chokidar/node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chokidar/node_modules/is-extendable": { + "version": "0.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chokidar/node_modules/is-number": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chokidar/node_modules/kind-of": { + "version": "3.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chokidar/node_modules/to-regex-range": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/class-utils": { + "version": "0.3.6", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/define-property": { + "version": "0.2.5", + "dev": true, + "license": "MIT", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-descriptor": { + "version": "0.1.7", + "dev": true, + "license": "MIT", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/clean-css": { + "version": "4.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table": { + "version": "0.3.11", + "dev": true, + "dependencies": { + "colors": "1.0.3" + }, + "engines": { + "node": ">= 0.2.0" + } + }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "10.3.0", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/cli-width": { + "version": "3.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-buffer": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/clone-stats": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/cloneable-readable": { + "version": "1.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + } + }, + "node_modules/cloneable-readable/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/cloneable-readable/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/cloneable-readable/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/cloneable-readable/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/code-point-at": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/collection-map": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-map": "^2.0.2", + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/collection-visit": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/color-support": { + "version": "1.1.3", + "dev": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/colorette": { + "version": "2.0.20", + "dev": true, + "license": "MIT" + }, + "node_modules/colors": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/commitizen": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "cachedir": "2.3.0", + "cz-conventional-changelog": "3.3.0", + "dedent": "0.7.0", + "detect-indent": "6.1.0", + "find-node-modules": "^2.1.2", + "find-root": "1.1.0", + "fs-extra": "9.1.0", + "glob": "7.2.3", + "inquirer": "8.2.5", + "is-utf8": "^0.2.1", + "lodash": "4.17.21", + "minimist": "1.2.7", + "strip-bom": "4.0.0", + "strip-json-comments": "3.1.1" + }, + "bin": { + "commitizen": "bin/commitizen", + "cz": "bin/git-cz", + "git-cz": "bin/git-cz" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/compare-func": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "node_modules/component-emitter": { + "version": "1.3.1", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "2.0.0", + "dev": true, + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-with-sourcemaps": { + "version": "1.1.0", + "dev": true, + "license": "ISC", + "dependencies": { + "source-map": "^0.6.1" + } + }, + "node_modules/config-chain": { + "version": "1.1.13", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/configstore": { + "version": "6.0.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dot-prop": "^6.0.1", + "graceful-fs": "^4.2.6", + "unique-string": "^3.0.0", + "write-file-atomic": "^3.0.3", + "xdg-basedir": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/yeoman/configstore?sponsor=1" + } + }, + "node_modules/configstore/node_modules/dot-prop": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/conventional-changelog": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "conventional-changelog-angular": "^7.0.0", + "conventional-changelog-atom": "^4.0.0", + "conventional-changelog-codemirror": "^4.0.0", + "conventional-changelog-conventionalcommits": "^7.0.2", + "conventional-changelog-core": "^7.0.0", + "conventional-changelog-ember": "^4.0.0", + "conventional-changelog-eslint": "^5.0.0", + "conventional-changelog-express": "^4.0.0", + "conventional-changelog-jquery": "^5.0.0", + "conventional-changelog-jshint": "^4.0.0", + "conventional-changelog-preset-loader": "^4.1.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-changelog-angular": { + "version": "7.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-changelog-atom": { + "version": "4.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-changelog-codemirror": { + "version": "4.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-changelog-conventionalcommits": { + "version": "7.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-changelog-core": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@hutson/parse-repository-url": "^5.0.0", + "add-stream": "^1.0.0", + "conventional-changelog-writer": "^7.0.0", + "conventional-commits-parser": "^5.0.0", + "git-raw-commits": "^4.0.0", + "git-semver-tags": "^7.0.0", + "hosted-git-info": "^7.0.0", + "normalize-package-data": "^6.0.0", + "read-pkg": "^8.0.0", + "read-pkg-up": "^10.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-changelog-core/node_modules/find-up": { + "version": "6.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/conventional-changelog-core/node_modules/hosted-git-info": { + "version": "7.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/conventional-changelog-core/node_modules/json-parse-even-better-errors": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/conventional-changelog-core/node_modules/lines-and-columns": { + "version": "2.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/conventional-changelog-core/node_modules/locate-path": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/conventional-changelog-core/node_modules/lru-cache": { + "version": "10.1.0", + "dev": true, + "license": "ISC", + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/conventional-changelog-core/node_modules/normalize-package-data": { + "version": "6.0.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/conventional-changelog-core/node_modules/p-limit": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/conventional-changelog-core/node_modules/p-locate": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/conventional-changelog-core/node_modules/parse-json": { + "version": "7.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.21.4", + "error-ex": "^1.3.2", + "json-parse-even-better-errors": "^3.0.0", + "lines-and-columns": "^2.0.3", + "type-fest": "^3.8.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/conventional-changelog-core/node_modules/parse-json/node_modules/type-fest": { + "version": "3.13.1", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/conventional-changelog-core/node_modules/path-exists": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/conventional-changelog-core/node_modules/read-pkg": { + "version": "8.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.1", + "normalize-package-data": "^6.0.0", + "parse-json": "^7.0.0", + "type-fest": "^4.2.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/conventional-changelog-core/node_modules/read-pkg-up": { + "version": "10.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^6.3.0", + "read-pkg": "^8.1.0", + "type-fest": "^4.2.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/conventional-changelog-core/node_modules/type-fest": { + "version": "4.9.0", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/conventional-changelog-core/node_modules/yocto-queue": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/conventional-changelog-ember": { + "version": "4.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-changelog-eslint": { + "version": "5.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-changelog-express": { + "version": "4.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-changelog-jquery": { + "version": "5.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-changelog-jshint": { + "version": "4.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-changelog-preset-loader": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-changelog-writer": { + "version": "7.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "conventional-commits-filter": "^4.0.0", + "handlebars": "^4.7.7", + "json-stringify-safe": "^5.0.1", + "meow": "^12.0.1", + "semver": "^7.5.2", + "split2": "^4.0.0" + }, + "bin": { + "conventional-changelog-writer": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-commit-types": { + "version": "3.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/conventional-commits-filter": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-commits-parser": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-text-path": "^2.0.0", + "JSONStream": "^1.3.5", + "meow": "^12.0.1", + "split2": "^4.0.0" + }, + "bin": { + "conventional-commits-parser": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-recommended-bump": { + "version": "9.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "conventional-changelog-preset-loader": "^4.1.0", + "conventional-commits-filter": "^4.0.0", + "conventional-commits-parser": "^5.0.0", + "git-raw-commits": "^4.0.0", + "git-semver-tags": "^7.0.0", + "meow": "^12.0.1" + }, + "bin": { + "conventional-recommended-bump": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/convert-source-map": { + "version": "0.3.5", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "2.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/copy-descriptor": { + "version": "0.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/copy-props": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "each-props": "^1.3.2", + "is-plain-object": "^5.0.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig-typescript-loader": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "jiti": "^1.19.1" + }, + "engines": { + "node": ">=v16" + }, + "peerDependencies": { + "@types/node": "*", + "cosmiconfig": ">=8.2", + "typescript": ">=4" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/crypto-random-string/node_modules/type-fest": { + "version": "1.4.0", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/css": { + "version": "2.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "source-map": "^0.6.1", + "source-map-resolve": "^0.5.2", + "urix": "^0.1.0" + } + }, + "node_modules/cz-conventional-changelog": { + "version": "3.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^2.4.1", + "commitizen": "^4.0.3", + "conventional-commit-types": "^3.0.0", + "lodash.map": "^4.5.1", + "longest": "^2.0.1", + "word-wrap": "^1.0.3" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@commitlint/load": ">6.1.1" + } + }, + "node_modules/d": { + "version": "1.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/dargs": { + "version": "8.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/dateformat": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dedent": { + "version": "0.7.0", + "dev": true, + "license": "MIT" + }, + "node_modules/deep-assign": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-assign/node_modules/is-obj": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-browser": { + "version": "5.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-compare": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^5.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-resolution": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-property": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/del": { + "version": "6.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "globby": "^11.0.1", + "graceful-fs": "^4.2.4", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.2", + "p-map": "^4.0.0", + "rimraf": "^3.0.2", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/del/node_modules/globby": { + "version": "11.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/del/node_modules/rimraf": { + "version": "3.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/del/node_modules/slash": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/deprecation": { + "version": "2.3.1", + "dev": true, + "license": "ISC" + }, + "node_modules/detect-file": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-indent": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "1.0.8", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/duplexer2": { + "version": "0.0.2", + "dev": true, + "license": "BSD", + "dependencies": { + "readable-stream": "~1.1.9" + } + }, + "node_modules/duplexer2/node_modules/isarray": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "1.1.14", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "0.10.31", + "dev": true, + "license": "MIT" + }, + "node_modules/duplexify": { + "version": "3.7.1", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/duplexify/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/duplexify/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexify/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/duplexify/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/each-props": { + "version": "1.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.1", + "object.defaults": "^1.1.0" + } + }, + "node_modules/each-props/node_modules/is-plain-object": { + "version": "2.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/editorconfig": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/brace-expansion": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/editorconfig/node_modules/commander": { + "version": "10.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/minimatch": { + "version": "9.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.763", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/errno": { + "version": "0.1.8", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es5-ext": { + "version": "0.10.64", + "dev": true, + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "dev": true, + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/es6-weak-map": { + "version": "2.0.3", + "dev": true, + "license": "ISC", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-goat": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esniff": { + "version": "2.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/execa": { + "version": "5.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/expand-brackets": { + "version": "2.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/debug": { + "version": "2.6.9", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/expand-brackets/node_modules/define-property": { + "version": "0.2.5", + "dev": true, + "license": "MIT", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/extend-shallow": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-descriptor": { + "version": "0.1.7", + "dev": true, + "license": "MIT", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/expand-brackets/node_modules/is-extendable": { + "version": "0.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/ms": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ext": { + "version": "1.7.0", + "dev": true, + "license": "ISC", + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/extend-shallow": { + "version": "3.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/external-editor": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extglob": { + "version": "2.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/define-property": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/extend-shallow": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-extendable": { + "version": "0.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fancy-log": { + "version": "1.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", + "time-stamp": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-levenshtein": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.16.0", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-node-modules": { + "version": "2.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "findup-sync": "^4.0.0", + "merge": "^2.1.1" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/findup-sync": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^4.0.2", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fined": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/fined/node_modules/is-plain-object": { + "version": "2.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/first-chunk-stream": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/first-chunk-stream/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/first-chunk-stream/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/first-chunk-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/first-chunk-stream/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/flagged-respawn": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/flush-write-stream": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "node_modules/flush-write-stream/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/flush-write-stream/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/flush-write-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/flush-write-stream/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/fomantic-ui": { + "version": "2.9.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@actions/core": "^1.6.0", + "@octokit/core": "^3.6.0", + "@octokit/rest": "^18.12.0", + "better-console": "^1.0.1", + "browserslist": "^4.21.4", + "del": "^6.1.1", + "extend": "^3.0.2", + "gulp": "^4.0.0", + "gulp-autoprefixer": "^8.0.0", + "gulp-chmod": "^2.0.0", + "gulp-clean-css": "^4.3.0", + "gulp-clone": "^2.0.1", + "gulp-concat": "^2.6.1", + "gulp-concat-css": "^3.1.0", + "gulp-concat-filenames": "^1.2.0", + "gulp-copy": "^4.0.0", + "gulp-dedupe": "^0.0.2", + "gulp-flatten": "^0.4.0", + "gulp-git": "^2.9.0", + "gulp-header": "^2.0.5", + "gulp-if": "^2.0.2", + "gulp-json-editor": "^2.4.3", + "gulp-less": "^5.0.0", + "gulp-plumber": "^1.1.0", + "gulp-print": "^5.0.0", + "gulp-rename": "^1.4.0", + "gulp-replace": "^1.0.0", + "gulp-rtlcss": "^2.0.0", + "gulp-tap": "^1.0.1", + "gulp-uglify": "^3.0.1", + "inquirer": "^8.2.0", + "jquery": "^3.4.0", + "less": "^3.12.0 || ^4.0.0", + "map-stream": "^0.1.0", + "merge-stream": "^2.0.0", + "mkdirp": "^1.0.4", + "normalize-path": "^3.0.0", + "replace-ext": "^1.0.0", + "require-dot-file": "^0.4.0", + "wrench-sui": "^0.0.3", + "yamljs": "^0.3.0" + }, + "engines": { + "node": ">=12", + "npm": ">=6.14.8" + } + }, + "node_modules/fomantic-ui/node_modules/@octokit/auth-token": { + "version": "2.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^6.0.3" + } + }, + "node_modules/fomantic-ui/node_modules/@octokit/core": { + "version": "3.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^2.4.4", + "@octokit/graphql": "^4.5.8", + "@octokit/request": "^5.6.3", + "@octokit/request-error": "^2.0.5", + "@octokit/types": "^6.0.3", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/fomantic-ui/node_modules/@octokit/endpoint": { + "version": "6.0.12", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/fomantic-ui/node_modules/@octokit/graphql": { + "version": "4.8.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request": "^5.6.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/fomantic-ui/node_modules/@octokit/openapi-types": { + "version": "12.11.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fomantic-ui/node_modules/@octokit/plugin-paginate-rest": { + "version": "2.21.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^6.40.0" + }, + "peerDependencies": { + "@octokit/core": ">=2" + } + }, + "node_modules/fomantic-ui/node_modules/@octokit/plugin-request-log": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/fomantic-ui/node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "5.16.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^6.39.0", + "deprecation": "^2.3.1" + }, + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/fomantic-ui/node_modules/@octokit/request": { + "version": "5.6.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.1.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/fomantic-ui/node_modules/@octokit/request-error": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "node_modules/fomantic-ui/node_modules/@octokit/rest": { + "version": "18.12.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/core": "^3.5.1", + "@octokit/plugin-paginate-rest": "^2.16.8", + "@octokit/plugin-request-log": "^1.0.4", + "@octokit/plugin-rest-endpoint-methods": "^5.12.0" + } + }, + "node_modules/fomantic-ui/node_modules/@octokit/types": { + "version": "6.41.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^12.11.0" + } + }, + "node_modules/fomantic-ui/node_modules/node-fetch": { + "version": "2.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/foreground-child": { + "version": "3.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fork-stream": { + "version": "0.0.4", + "dev": true, + "license": "BSD" + }, + "node_modules/form-data-encoder": { + "version": "2.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.17" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fragment-cache": { + "version": "0.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "map-cache": "^0.2.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fs-extra": { + "version": "9.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fs-mkdirp-stream": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.11", + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/fs-mkdirp-stream/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fs-mkdirp-stream/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/fs-mkdirp-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/fs-mkdirp-stream/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/fs-mkdirp-stream/node_modules/through2": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-imports": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "array-uniq": "^1.0.1", + "import-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4", + "fs-extra": "^11.2.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/fs-extra": { + "version": "11.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/get-value": { + "version": "2.0.6", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/git-raw-commits": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "dargs": "^8.0.0", + "meow": "^12.0.1", + "split2": "^4.0.0" + }, + "bin": { + "git-raw-commits": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/git-semver-tags": { + "version": "7.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "meow": "^12.0.1", + "semver": "^7.5.2" + }, + "bin": { + "git-semver-tags": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/git-up": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-ssh": "^1.4.0", + "parse-url": "^8.1.0" + } + }, + "node_modules/git-url-parse": { + "version": "14.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "git-up": "^7.0.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-stream": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "extend": "^3.0.0", + "glob": "^7.1.1", + "glob-parent": "^3.1.0", + "is-negated-glob": "^1.0.0", + "ordered-read-streams": "^1.0.0", + "pumpify": "^1.3.5", + "readable-stream": "^2.1.5", + "remove-trailing-separator": "^1.0.1", + "to-absolute-glob": "^2.0.0", + "unique-stream": "^2.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/glob-stream/node_modules/glob-parent": { + "version": "3.1.0", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/glob-stream/node_modules/is-glob": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-stream/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/glob-stream/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/glob-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/glob-stream/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/glob-watcher": { + "version": "5.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "^2.0.0", + "async-done": "^1.2.0", + "chokidar": "^2.0.0", + "is-negated-glob": "^1.0.0", + "just-debounce": "^1.0.0", + "normalize-path": "^3.0.0", + "object.defaults": "^1.1.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/global-directory": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "4.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/global-directory/node_modules/ini": { + "version": "4.1.1", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/global-modules": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/globby": { + "version": "14.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/path-type": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glogg": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "sparkles": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "13.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "license": "ISC" + }, + "node_modules/gulp": { + "version": "4.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-watcher": "^5.0.3", + "gulp-cli": "^2.2.0", + "undertaker": "^1.2.1", + "vinyl-fs": "^3.0.0" + }, + "bin": { + "gulp": "bin/gulp.js" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-autoprefixer": { + "version": "8.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "autoprefixer": "^10.2.6", + "fancy-log": "^1.3.3", + "plugin-error": "^1.0.1", + "postcss": "^8.3.0", + "through2": "^4.0.2", + "vinyl-sourcemaps-apply": "^0.2.1" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "gulp": ">=4" + }, + "peerDependenciesMeta": { + "gulp": { + "optional": true + } + } + }, + "node_modules/gulp-chmod": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-assign": "^1.0.0", + "stat-mode": "^0.2.0", + "through2": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/gulp-chmod/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-chmod/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/gulp-chmod/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-chmod/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/gulp-chmod/node_modules/through2": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-clean-css": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-css": "4.2.3", + "plugin-error": "1.0.1", + "through2": "3.0.1", + "vinyl-sourcemaps-apply": "0.2.1" + } + }, + "node_modules/gulp-clean-css/node_modules/through2": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "2 || 3" + } + }, + "node_modules/gulp-cli": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^1.0.1", + "archy": "^1.0.0", + "array-sort": "^1.0.0", + "color-support": "^1.1.3", + "concat-stream": "^1.6.0", + "copy-props": "^2.0.1", + "fancy-log": "^1.3.2", + "gulplog": "^1.0.0", + "interpret": "^1.4.0", + "isobject": "^3.0.1", + "liftoff": "^3.1.0", + "matchdep": "^2.0.0", + "mute-stdout": "^1.0.0", + "pretty-hrtime": "^1.0.0", + "replace-homedir": "^1.0.0", + "semver-greatest-satisfied-range": "^1.1.0", + "v8flags": "^3.2.0", + "yargs": "^7.1.0" + }, + "bin": { + "gulp": "bin/gulp.js" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-cli/node_modules/ansi-regex": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-cli/node_modules/cliui": { + "version": "3.2.0", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "node_modules/gulp-cli/node_modules/concat-stream": { + "version": "1.6.2", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/gulp-cli/node_modules/get-caller-file": { + "version": "1.0.3", + "dev": true, + "license": "ISC" + }, + "node_modules/gulp-cli/node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-cli/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-cli/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/gulp-cli/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-cli/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/gulp-cli/node_modules/string-width": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-cli/node_modules/strip-ansi": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-cli/node_modules/wrap-ansi": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-cli/node_modules/y18n": { + "version": "3.2.2", + "dev": true, + "license": "ISC" + }, + "node_modules/gulp-cli/node_modules/yargs": { + "version": "7.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^5.0.1" + } + }, + "node_modules/gulp-cli/node_modules/yargs-parser": { + "version": "5.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^3.0.0", + "object.assign": "^4.1.0" + } + }, + "node_modules/gulp-clone": { + "version": "2.0.1", + "dev": true, + "dependencies": { + "plugin-error": "^0.1.2", + "through2": "^2.0.3" + } + }, + "node_modules/gulp-clone/node_modules/arr-diff": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-clone/node_modules/arr-union": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-clone/node_modules/array-slice": { + "version": "0.2.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-clone/node_modules/extend-shallow": { + "version": "1.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-clone/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-clone/node_modules/kind-of": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-clone/node_modules/plugin-error": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-clone/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/gulp-clone/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-clone/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/gulp-clone/node_modules/through2": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-concat": { + "version": "2.6.1", + "dev": true, + "license": "MIT", + "dependencies": { + "concat-with-sourcemaps": "^1.0.0", + "through2": "^2.0.0", + "vinyl": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-concat-css": { + "version": "3.1.0", + "dev": true, + "dependencies": { + "lodash.defaults": "^3.0.0", + "parse-import": "^2.0.0", + "plugin-error": "^0.1.2", + "rework": "~1.0.0", + "rework-import": "^2.0.0", + "rework-plugin-url": "^1.0.1", + "through2": "~1.1.1", + "vinyl": "^2.1.0" + } + }, + "node_modules/gulp-concat-css/node_modules/arr-diff": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-concat-css/node_modules/arr-union": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-concat-css/node_modules/array-slice": { + "version": "0.2.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-concat-css/node_modules/extend-shallow": { + "version": "1.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-concat-css/node_modules/isarray": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-concat-css/node_modules/kind-of": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-concat-css/node_modules/plugin-error": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-concat-css/node_modules/readable-stream": { + "version": "1.1.14", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/gulp-concat-css/node_modules/string_decoder": { + "version": "0.10.31", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-concat-css/node_modules/through2": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": ">=1.1.13-1 <1.2.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + }, + "node_modules/gulp-concat-filenames": { + "version": "1.2.0", + "dev": true, + "dependencies": { + "gulp-util": "3.x.x", + "through": "2.x.x" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-concat/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-concat/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/gulp-concat/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-concat/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/gulp-concat/node_modules/through2": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-copy": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "gulp": "^4.0.0", + "plugin-error": "^0.1.2", + "through2": "^2.0.3" + } + }, + "node_modules/gulp-copy/node_modules/arr-diff": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-copy/node_modules/arr-union": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-copy/node_modules/array-slice": { + "version": "0.2.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-copy/node_modules/extend-shallow": { + "version": "1.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-copy/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-copy/node_modules/kind-of": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-copy/node_modules/plugin-error": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-copy/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/gulp-copy/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-copy/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/gulp-copy/node_modules/through2": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-dedupe": { + "version": "0.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "colors": "~1.0.2", + "diff": "~1.0.8", + "gulp-util": "~3.0.1", + "lodash.defaults": "~2.4.1", + "through": "~2.3.6" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/gulp-dedupe/node_modules/lodash.defaults": { + "version": "2.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash._objecttypes": "~2.4.1", + "lodash.keys": "~2.4.1" + } + }, + "node_modules/gulp-dedupe/node_modules/lodash.keys": { + "version": "2.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash._isnative": "~2.4.1", + "lodash._shimkeys": "~2.4.1", + "lodash.isobject": "~2.4.1" + } + }, + "node_modules/gulp-flatten": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "plugin-error": "^0.1.2", + "through2": "^2.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/gulp-flatten/node_modules/arr-diff": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-flatten/node_modules/arr-union": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-flatten/node_modules/array-slice": { + "version": "0.2.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-flatten/node_modules/extend-shallow": { + "version": "1.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-flatten/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-flatten/node_modules/kind-of": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-flatten/node_modules/plugin-error": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-flatten/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/gulp-flatten/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-flatten/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/gulp-flatten/node_modules/through2": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-git": { + "version": "2.10.1", + "dev": true, + "license": "MIT", + "dependencies": { + "any-shell-escape": "^0.1.1", + "fancy-log": "^1.3.2", + "lodash.template": "^4.4.0", + "plugin-error": "^1.0.1", + "require-dir": "^1.0.0", + "strip-bom-stream": "^3.0.0", + "through2": "^2.0.3", + "vinyl": "^2.0.1" + }, + "engines": { + "node": ">= 0.9.0" + } + }, + "node_modules/gulp-git/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-git/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/gulp-git/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-git/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/gulp-git/node_modules/through2": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-header": { + "version": "2.0.9", + "dev": true, + "license": "MIT", + "dependencies": { + "concat-with-sourcemaps": "^1.1.0", + "lodash.template": "^4.5.0", + "map-stream": "0.0.7", + "through2": "^2.0.0" + } + }, + "node_modules/gulp-header/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-header/node_modules/map-stream": { + "version": "0.0.7", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-header/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/gulp-header/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-header/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/gulp-header/node_modules/through2": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-if": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "gulp-match": "^1.0.3", + "ternary-stream": "^2.0.1", + "through2": "^2.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/gulp-if/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-if/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/gulp-if/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-if/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/gulp-if/node_modules/through2": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-json-editor": { + "version": "2.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "deepmerge": "^4.3.1", + "detect-indent": "^6.1.0", + "js-beautify": "^1.14.11", + "plugin-error": "^2.0.1", + "through2": "^4.0.2" + } + }, + "node_modules/gulp-json-editor/node_modules/plugin-error": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^1.0.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp-less": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "less": "^3.7.1 || ^4.0.0", + "object-assign": "^4.0.1", + "plugin-error": "^1.0.0", + "replace-ext": "^2.0.0", + "through2": "^4.0.0", + "vinyl-sourcemaps-apply": "^0.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/gulp-less/node_modules/replace-ext": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/gulp-match": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "minimatch": "^3.0.3" + } + }, + "node_modules/gulp-plumber": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^1.1.3", + "fancy-log": "^1.3.2", + "plugin-error": "^0.1.2", + "through2": "^2.0.3" + }, + "engines": { + "node": ">=0.10", + "npm": ">=1.2.10" + } + }, + "node_modules/gulp-plumber/node_modules/ansi-regex": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-plumber/node_modules/ansi-styles": { + "version": "2.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-plumber/node_modules/arr-diff": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-plumber/node_modules/arr-union": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-plumber/node_modules/array-slice": { + "version": "0.2.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-plumber/node_modules/chalk": { + "version": "1.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-plumber/node_modules/extend-shallow": { + "version": "1.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-plumber/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-plumber/node_modules/kind-of": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-plumber/node_modules/plugin-error": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-plumber/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/gulp-plumber/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-plumber/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/gulp-plumber/node_modules/strip-ansi": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-plumber/node_modules/supports-color": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/gulp-plumber/node_modules/through2": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-print": { + "version": "5.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^3.2.4", + "fancy-log": "^1.3.3", + "map-stream": "0.0.7", + "vinyl": "^2.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/gulp-print/node_modules/ansi-colors": { + "version": "3.2.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/gulp-print/node_modules/map-stream": { + "version": "0.0.7", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-rename": { + "version": "1.4.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/gulp-replace": { + "version": "1.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/vinyl": "^2.0.4", + "istextorbinary": "^3.0.0", + "replacestream": "^4.0.3", + "yargs-parser": ">=5.0.0-security.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gulp-rtlcss": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "plugin-error": "^1.0.1", + "rtlcss": "^3.5.0", + "through2": "^2.0.5", + "vinyl-sourcemaps-apply": "^0.2.1" + } + }, + "node_modules/gulp-rtlcss/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-rtlcss/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/gulp-rtlcss/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-rtlcss/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/gulp-rtlcss/node_modules/through2": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-tap": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "through2": "^2.0.3" + } + }, + "node_modules/gulp-tap/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-tap/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/gulp-tap/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-tap/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/gulp-tap/node_modules/through2": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-uglify": { + "version": "3.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "array-each": "^1.0.1", + "extend-shallow": "^3.0.2", + "gulplog": "^1.0.0", + "has-gulplog": "^0.1.0", + "isobject": "^3.0.1", + "make-error-cause": "^1.1.1", + "safe-buffer": "^5.1.2", + "through2": "^2.0.0", + "uglify-js": "^3.0.5", + "vinyl-sourcemaps-apply": "^0.2.0" + } + }, + "node_modules/gulp-uglify/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-uglify/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/gulp-uglify/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-uglify/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/gulp-uglify/node_modules/through2": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-util": { + "version": "3.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "array-differ": "^1.0.0", + "array-uniq": "^1.0.2", + "beeper": "^1.0.0", + "chalk": "^1.0.0", + "dateformat": "^2.0.0", + "fancy-log": "^1.1.0", + "gulplog": "^1.0.0", + "has-gulplog": "^0.1.0", + "lodash._reescape": "^3.0.0", + "lodash._reevaluate": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.template": "^3.0.0", + "minimist": "^1.1.0", + "multipipe": "^0.1.2", + "object-assign": "^3.0.0", + "replace-ext": "0.0.1", + "through2": "^2.0.0", + "vinyl": "^0.5.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/gulp-util/node_modules/ansi-regex": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-util/node_modules/ansi-styles": { + "version": "2.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-util/node_modules/chalk": { + "version": "1.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-util/node_modules/clone-stats": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-util/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-util/node_modules/lodash.template": { + "version": "3.6.2", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash._basecopy": "^3.0.0", + "lodash._basetostring": "^3.0.0", + "lodash._basevalues": "^3.0.0", + "lodash._isiterateecall": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0", + "lodash.keys": "^3.0.0", + "lodash.restparam": "^3.0.0", + "lodash.templatesettings": "^3.0.0" + } + }, + "node_modules/gulp-util/node_modules/lodash.templatesettings": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0" + } + }, + "node_modules/gulp-util/node_modules/object-assign": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-util/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/gulp-util/node_modules/replace-ext": { + "version": "0.0.1", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gulp-util/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-util/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/gulp-util/node_modules/strip-ansi": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-util/node_modules/supports-color": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/gulp-util/node_modules/through2": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/gulp-util/node_modules/vinyl": { + "version": "0.5.3", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.0", + "clone-stats": "^0.0.1", + "replace-ext": "0.0.1" + }, + "engines": { + "node": ">= 0.9" + } + }, + "node_modules/gulplog": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "glogg": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/handlebars": { + "version": "4.7.8", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-ansi/node_modules/ansi-regex": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-gulplog": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "sparkles": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-value": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-number": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/kind-of": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "dev": true, + "license": "ISC" + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http2-wrapper": { + "version": "2.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/http2-wrapper/node_modules/quick-lru": { + "version": "5.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/husky": { + "version": "9.1.4", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.4.tgz", + "integrity": "sha512-bho94YyReb4JV7LYWRWxZ/xr6TtOTt8cMfmQ39MQYJ7f/YE268s3GdghGwi+y4zAeqewE5zYLvuhV0M0ijsDEA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/image-size": { + "version": "0.5.5", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/import-lazy": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/import-regex": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "dev": true, + "license": "ISC" + }, + "node_modules/inquirer": { + "version": "8.2.5", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/inquirer/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/inquirer/node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/inquirer/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/inquirer/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/inquirer/node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/interpret": { + "version": "1.4.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/invert-kv": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ip-regex": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-absolute": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-accessor-descriptor": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "dev": true, + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "dev": true, + "license": "MIT" + }, + "node_modules/is-ci": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-descriptor": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-descriptor": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extendable": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extendable/node_modules/is-plain-object": { + "version": "2.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-in-ci": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "bin": { + "is-in-ci": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-negated-glob": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-npm": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-path-cwd": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-relative": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unc-path": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-ssh": { + "version": "1.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "protocols": "^2.0.1" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-text-path": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "text-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/is-unc-path": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "unc-path-regex": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-utf8": { + "version": "0.2.1", + "dev": true, + "license": "MIT" + }, + "node_modules/is-valid-glob": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-what": { + "version": "3.14.1", + "dev": true, + "license": "MIT" + }, + "node_modules/is-windows": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/issue-parser": { + "version": "7.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.capitalize": "^4.2.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.uniqby": "^4.7.0" + }, + "engines": { + "node": "^18.17 || >=20.6.1" + } + }, + "node_modules/istextorbinary": { + "version": "3.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "binaryextensions": "^2.2.0", + "textextensions": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/jackspeak": { + "version": "2.3.6", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "1.21.0", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/jquery": { + "version": "3.7.1", + "dev": true, + "license": "MIT" + }, + "node_modules/js-beautify": { + "version": "1.15.1", + "dev": true, + "license": "MIT", + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.3.3", + "js-cookie": "^3.0.5", + "nopt": "^7.2.0" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-beautify/node_modules/abbrev": { + "version": "2.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/js-beautify/node_modules/brace-expansion": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/js-beautify/node_modules/glob": { + "version": "10.3.15", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.6", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.11.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-beautify/node_modules/minimatch": { + "version": "9.0.4", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-beautify/node_modules/minipass": { + "version": "7.1.1", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/js-beautify/node_modules/nopt": { + "version": "7.2.1", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "dev": true, + "license": "ISC" + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "dev": true, + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/just-debounce": { + "version": "1.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ky": { + "version": "1.4.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/ky?sponsor=1" + } + }, + "node_modules/last-run": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "default-resolution": "^2.0.0", + "es6-weak-map": "^2.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/lazystream": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/lcid": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "invert-kv": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lead": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "flush-write-stream": "^1.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/less": { + "version": "4.2.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=6" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/liftoff": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "extend": "^3.0.0", + "findup-sync": "^3.0.0", + "fined": "^1.0.1", + "flagged-respawn": "^1.0.0", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.0", + "rechoir": "^0.6.2", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/liftoff/node_modules/braces": { + "version": "2.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/liftoff/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/liftoff/node_modules/fill-range": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/liftoff/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/liftoff/node_modules/findup-sync": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/liftoff/node_modules/is-extendable": { + "version": "0.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/liftoff/node_modules/is-number": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/liftoff/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/liftoff/node_modules/is-plain-object": { + "version": "2.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/liftoff/node_modules/kind-of": { + "version": "6.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/liftoff/node_modules/micromatch": { + "version": "3.1.10", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/liftoff/node_modules/to-regex-range": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lilconfig": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "dev": true, + "license": "MIT" + }, + "node_modules/lint-staged": { + "version": "15.2.7", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "~5.3.0", + "commander": "~12.1.0", + "debug": "~4.3.4", + "execa": "~8.0.1", + "lilconfig": "~3.1.1", + "listr2": "~8.2.1", + "micromatch": "~4.0.7", + "pidtree": "~0.6.0", + "string-argv": "~0.3.2", + "yaml": "~2.4.2" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/chalk": { + "version": "5.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/execa": { + "version": "8.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/get-stream": { + "version": "8.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/human-signals": { + "version": "5.0.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/lint-staged/node_modules/is-stream": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/mimic-fn": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/npm-run-path": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/onetime": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/path-key": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/lint-staged/node_modules/strip-final-newline": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2": { + "version": "8.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.0.0", + "rfdc": "^1.3.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-regex": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "10.3.0", + "dev": true, + "license": "MIT" + }, + "node_modules/listr2/node_modules/string-width": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/load-json-file": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/load-json-file/node_modules/parse-json": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "error-ex": "^1.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/load-json-file/node_modules/pify": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/load-json-file/node_modules/strip-bom": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-utf8": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash._baseassign": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash._basecopy": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "node_modules/lodash._basecopy": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash._basetostring": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash._basevalues": { + "version": "3.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash._bindcallback": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash._createassigner": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash._bindcallback": "^3.0.0", + "lodash._isiterateecall": "^3.0.0", + "lodash.restparam": "^3.0.0" + } + }, + "node_modules/lodash._getnative": { + "version": "3.9.1", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash._isiterateecall": { + "version": "3.0.9", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash._isnative": { + "version": "2.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash._objecttypes": { + "version": "2.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash._reescape": { + "version": "3.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash._reevaluate": { + "version": "3.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash._reinterpolate": { + "version": "3.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash._root": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash._shimkeys": { + "version": "2.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash._objecttypes": "~2.4.1" + } + }, + "node_modules/lodash.assign": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash._baseassign": "^3.0.0", + "lodash._createassigner": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.capitalize": { + "version": "4.2.1", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.defaults": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.assign": "^3.0.0", + "lodash.restparam": "^3.0.0" + } + }, + "node_modules/lodash.escape": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash._root": "^3.0.0" + } + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isarray": { + "version": "3.0.4", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isobject": { + "version": "2.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash._objecttypes": "~2.4.1" + } + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.kebabcase": { + "version": "4.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.keys": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "node_modules/lodash.map": { + "version": "4.6.0", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.restparam": { + "version": "3.6.1", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.startcase": { + "version": "4.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.template": { + "version": "4.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash._reinterpolate": "^3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "node_modules/lodash.templatesettings": { + "version": "4.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash._reinterpolate": "^3.0.0" + } + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.uniqby": { + "version": "4.7.0", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.upperfirst": { + "version": "4.3.1", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols/node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-update": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^6.2.0", + "cli-cursor": "^4.0.0", + "slice-ansi": "^7.0.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-escapes": { + "version": "6.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/cli-cursor": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.3.0", + "dev": true, + "license": "MIT" + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/restore-cursor": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/longest": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lowercase-keys": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/macos-release": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.2", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "dev": true, + "license": "ISC" + }, + "node_modules/make-error-cause": { + "version": "1.2.2", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "make-error": "^1.2.0" + } + }, + "node_modules/make-iterator": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/make-iterator/node_modules/kind-of": { + "version": "6.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-stream": { + "version": "0.1.0", + "dev": true + }, + "node_modules/map-visit": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "findup-sync": "^2.0.0", + "micromatch": "^3.0.4", + "resolve": "^1.4.0", + "stack-trace": "0.0.10" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/matchdep/node_modules/braces": { + "version": "2.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/fill-range": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/findup-sync": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^3.1.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/matchdep/node_modules/is-extendable": { + "version": "0.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/is-glob": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/is-number": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/kind-of": { + "version": "6.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/micromatch": { + "version": "3.1.10", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/matchdep/node_modules/to-regex-range": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/meow": { + "version": "12.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge": { + "version": "2.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.7", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/mixin-deep": { + "version": "1.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/multipipe": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexer2": "0.0.2" + } + }, + "node_modules/mute-stdout": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "dev": true, + "license": "ISC" + }, + "node_modules/nanoid": { + "version": "3.3.7", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nanomatch": { + "version": "1.2.13", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/kind-of": { + "version": "6.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/needle": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/iconv-lite": { + "version": "0.6.3", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/netmask": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/new-github-release-url": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^2.5.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/new-github-release-url/node_modules/type-fest": { + "version": "2.19.0", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/next-tick": { + "version": "1.1.0", + "dev": true, + "license": "ISC" + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-releases": { + "version": "2.0.14", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "8.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/now-and-later": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.3.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/npm-check-updates": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-17.0.1.tgz", + "integrity": "sha512-PKVCncWsT/8rBptjds5lpGMLlqaTjv11NJgblMf2rKUauzHZBB9OSAqFgC4duyY2+N6na/WGzL9CUD/+ZeIzvQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "ncu": "build/cli.js", + "npm-check-updates": "build/cli.js" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0", + "npm": ">=8.12.1" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/define-property": { + "version": "0.2.5", + "dev": true, + "license": "MIT", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor": { + "version": "0.1.7", + "dev": true, + "license": "MIT", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object-copy/node_modules/kind-of": { + "version": "3.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object-visit": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.defaults": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.map": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.reduce": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "10.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ora/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/ora/node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ordered-read-streams": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.1" + } + }, + "node_modules/ordered-read-streams/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/ordered-read-streams/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/ordered-read-streams/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/ordered-read-streams/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/os-locale": { + "version": "1.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "lcid": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/os-name": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "macos-release": "^3.1.0", + "windows-release": "^5.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-cancelable": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "pac-resolver": "^7.0.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-filepath": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/parse-import": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "get-imports": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-path": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "protocols": "^2.0.0" + } + }, + "node_modules/parse-url": { + "version": "8.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-path": "^7.0.0" + } + }, + "node_modules/pascalcase": { + "version": "0.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-dirname": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "dev": true, + "license": "MIT" + }, + "node_modules/path-root": { + "version": "0.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "path-root-regex": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-root-regex": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.0", + "dev": true, + "license": "ISC", + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.6.0", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/plugin-error": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/posix-character-classes": { + "version": "0.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss": { + "version": "8.4.38", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/prettier": { + "version": "3.3.3", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-hrtime": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/proto-list": { + "version": "1.2.4", + "dev": true, + "license": "ISC" + }, + "node_modules/protocols": { + "version": "2.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/proxy-agent": { + "version": "6.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/prr": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/pump": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "1.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pupa": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-goat": "^4.0.0" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/rc": { + "version": "1.2.8", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg-up": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg-up/node_modules/path-exists": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg/node_modules/path-type": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg/node_modules/pify": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "2.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/readdirp/node_modules/braces": { + "version": "2.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/fill-range": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/is-extendable": { + "version": "0.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/is-number": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/readdirp/node_modules/kind-of": { + "version": "6.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/micromatch": { + "version": "3.1.10", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readdirp/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/readdirp/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/readdirp/node_modules/to-regex-range": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.6.2", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/regex-not": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/registry-auth-token": { + "version": "5.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@pnpm/npm-conf": "^2.1.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/registry-url": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "1.2.8" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/release-it": { + "version": "17.6.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/webpro" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/webpro" + } + ], + "license": "MIT", + "dependencies": { + "@iarna/toml": "2.2.5", + "@octokit/rest": "20.1.1", + "async-retry": "1.3.3", + "chalk": "5.3.0", + "cosmiconfig": "9.0.0", + "execa": "8.0.1", + "git-url-parse": "14.0.0", + "globby": "14.0.2", + "got": "13.0.0", + "inquirer": "9.3.2", + "is-ci": "3.0.1", + "issue-parser": "7.0.1", + "lodash": "4.17.21", + "mime-types": "2.1.35", + "new-github-release-url": "2.0.0", + "node-fetch": "3.3.2", + "open": "10.1.0", + "ora": "8.0.1", + "os-name": "5.1.0", + "proxy-agent": "6.4.0", + "semver": "7.6.2", + "shelljs": "0.8.5", + "update-notifier": "7.1.0", + "url-join": "5.0.0", + "wildcard-match": "5.1.3", + "yargs-parser": "21.1.1" + }, + "bin": { + "release-it": "bin/release-it.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || ^22.0.0" + } + }, + "node_modules/release-it/node_modules/ansi-regex": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/release-it/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/release-it/node_modules/chalk": { + "version": "5.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/release-it/node_modules/cli-width": { + "version": "4.1.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/release-it/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/release-it/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/release-it/node_modules/emoji-regex": { + "version": "10.3.0", + "dev": true, + "license": "MIT" + }, + "node_modules/release-it/node_modules/execa": { + "version": "8.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/release-it/node_modules/get-stream": { + "version": "8.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/release-it/node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/release-it/node_modules/human-signals": { + "version": "5.0.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/release-it/node_modules/inquirer": { + "version": "9.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/figures": "^1.0.3", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "external-editor": "^3.1.0", + "mute-stream": "1.0.0", + "ora": "^5.4.1", + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/release-it/node_modules/inquirer/node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/release-it/node_modules/inquirer/node_modules/ora": { + "version": "5.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/release-it/node_modules/is-stream": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/release-it/node_modules/mimic-fn": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/release-it/node_modules/mute-stream": { + "version": "1.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/release-it/node_modules/npm-run-path": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/release-it/node_modules/onetime": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/release-it/node_modules/ora": { + "version": "8.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^4.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/release-it/node_modules/ora/node_modules/cli-cursor": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/release-it/node_modules/ora/node_modules/is-interactive": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/release-it/node_modules/ora/node_modules/is-unicode-supported": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/release-it/node_modules/ora/node_modules/log-symbols": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/release-it/node_modules/ora/node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/release-it/node_modules/ora/node_modules/string-width": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/release-it/node_modules/ora/node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/release-it/node_modules/path-key": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/release-it/node_modules/restore-cursor": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/release-it/node_modules/restore-cursor/node_modules/mimic-fn": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/release-it/node_modules/restore-cursor/node_modules/onetime": { + "version": "5.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/release-it/node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "dev": true, + "license": "ISC" + }, + "node_modules/release-it/node_modules/run-async": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/release-it/node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/release-it/node_modules/strip-final-newline": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/release-it/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/release-it/node_modules/url-join": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/release-it/node_modules/wrap-ansi": { + "version": "6.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/remove-bom-buffer": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5", + "is-utf8": "^0.2.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/remove-bom-stream": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "remove-bom-buffer": "^3.0.0", + "safe-buffer": "^5.1.0", + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/remove-bom-stream/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/remove-bom-stream/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/remove-bom-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/remove-bom-stream/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/remove-bom-stream/node_modules/through2": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "dev": true, + "license": "ISC" + }, + "node_modules/repeat-element": { + "version": "1.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/replace-ext": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/replace-homedir": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "homedir-polyfill": "^1.0.1", + "is-absolute": "^1.0.0", + "remove-trailing-separator": "^1.1.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/replacestream": { + "version": "4.0.3", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "escape-string-regexp": "^1.0.3", + "object-assign": "^4.0.1", + "readable-stream": "^2.0.2" + } + }, + "node_modules/replacestream/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/replacestream/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/replacestream/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/replacestream/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/require-dir": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-dot-file": { + "version": "0.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "1.0.1", + "dev": true, + "license": "ISC" + }, + "node_modules/resolve": { + "version": "1.22.8", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve-dir": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-options": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "value-or-function": "^3.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/resolve-url": { + "version": "0.2.1", + "dev": true, + "license": "MIT" + }, + "node_modules/responselike": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ret": { + "version": "0.1.15", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rework": { + "version": "1.0.1", + "dev": true, + "dependencies": { + "convert-source-map": "^0.3.3", + "css": "^2.0.0" + } + }, + "node_modules/rework-import": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "css": "^2.0.0", + "globby": "^2.0.0", + "parse-import": "^2.0.0", + "url-regex": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rework-import/node_modules/array-union": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rework-import/node_modules/glob": { + "version": "5.0.15", + "dev": true, + "license": "ISC", + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/rework-import/node_modules/globby": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^1.0.1", + "async": "^1.2.1", + "glob": "^5.0.3", + "object-assign": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rework-import/node_modules/object-assign": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rework-plugin-function": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "rework-visit": "^1.0.0" + } + }, + "node_modules/rework-plugin-url": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "rework-plugin-function": "^1.0.0" + } + }, + "node_modules/rework-visit": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/rfdc": { + "version": "1.3.1", + "dev": true, + "license": "MIT" + }, + "node_modules/rtlcss": { + "version": "3.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^5.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.3.11", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "rtlcss": "bin/rtlcss.js" + } + }, + "node_modules/run-applescript": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-async": { + "version": "2.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ret": "~0.1.10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.3.0", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/semver": { + "version": "7.6.2", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-diff": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semver-greatest-satisfied-range": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "sver-compat": "^1.5.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/server": { + "resolved": "internal/app/server", + "link": true + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-value": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/extend-shallow": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/is-extendable": { + "version": "0.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/is-plain-object": { + "version": "2.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shelljs": { + "version": "0.8.5", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "dev": true, + "license": "ISC" + }, + "node_modules/slash": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/snapdragon": { + "version": "0.8.2", + "dev": true, + "license": "MIT", + "dependencies": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/define-property": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^3.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util/node_modules/kind-of": { + "version": "3.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/debug": { + "version": "2.6.9", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/snapdragon/node_modules/define-property": { + "version": "0.2.5", + "dev": true, + "license": "MIT", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/extend-shallow": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-descriptor": { + "version": "0.1.7", + "dev": true, + "license": "MIT", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/snapdragon/node_modules/is-extendable": { + "version": "0.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/ms": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/snapdragon/node_modules/source-map": { + "version": "0.5.7", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/socks": { + "version": "2.7.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/socks/node_modules/ip": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-resolve": { + "version": "0.5.3", + "dev": true, + "license": "MIT", + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "node_modules/source-map-url": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/sparkles": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.16", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/split-string": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/stat-mode": { + "version": "0.2.2", + "dev": true, + "license": "MIT" + }, + "node_modules/static-extend": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/define-property": { + "version": "0.2.5", + "dev": true, + "license": "MIT", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-descriptor": { + "version": "0.1.7", + "dev": true, + "license": "MIT", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stream-exhaust": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-argv": { + "version": "0.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom-buf": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-utf8": "^0.2.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-bom-stream": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "first-chunk-stream": "^2.0.0", + "strip-bom-buf": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sver-compat": { + "version": "1.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/ternary-stream": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexify": "^3.5.0", + "fork-stream": "^0.0.4", + "merge-stream": "^1.0.0", + "through2": "^2.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/ternary-stream/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/ternary-stream/node_modules/merge-stream": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.1" + } + }, + "node_modules/ternary-stream/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/ternary-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/ternary-stream/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/ternary-stream/node_modules/through2": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/text-extensions": { + "version": "2.4.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/textextensions": { + "version": "3.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/through": { + "version": "2.3.8", + "dev": true, + "license": "MIT" + }, + "node_modules/through2": { + "version": "4.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "3" + } + }, + "node_modules/through2-filter": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "through2": "~2.0.0", + "xtend": "~4.0.0" + } + }, + "node_modules/through2-filter/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/through2-filter/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/through2-filter/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/through2-filter/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/through2-filter/node_modules/through2": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/time-stamp": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tmp": { + "version": "0.0.33", + "dev": true, + "license": "MIT", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-absolute-glob": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path": { + "version": "0.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path/node_modules/kind-of": { + "version": "3.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex": { + "version": "3.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/to-through": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "through2": "^2.0.3" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/to-through/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/to-through/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/to-through/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/to-through/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/to-through/node_modules/through2": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.6.2", + "dev": true, + "license": "0BSD" + }, + "node_modules/tunnel": { + "version": "0.0.6", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/type": { + "version": "2.7.2", + "dev": true, + "license": "ISC" + }, + "node_modules/type-fest": { + "version": "0.21.3", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/uglify-js": { + "version": "3.17.4", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/unc-path-regex": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/undertaker": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "bach": "^1.0.0", + "collection-map": "^1.0.0", + "es6-weak-map": "^2.0.1", + "fast-levenshtein": "^1.0.0", + "last-run": "^1.1.0", + "object.defaults": "^1.0.0", + "object.reduce": "^1.0.0", + "undertaker-registry": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/undertaker-registry": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/undici": { + "version": "5.28.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/union-value": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/union-value/node_modules/is-extendable": { + "version": "0.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unique-stream": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "json-stable-stringify-without-jsonify": "^1.0.1", + "through2-filter": "^3.0.0" + } + }, + "node_modules/unique-string": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "crypto-random-string": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universal-user-agent": { + "version": "6.0.1", + "dev": true, + "license": "ISC" + }, + "node_modules/universalify": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unset-value": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value": { + "version": "0.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-values": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/upath": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.15", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/update-notifier": { + "version": "7.1.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boxen": "^7.1.1", + "chalk": "^5.3.0", + "configstore": "^6.0.0", + "import-lazy": "^4.0.0", + "is-in-ci": "^0.1.0", + "is-installed-globally": "^1.0.0", + "is-npm": "^6.0.0", + "latest-version": "^9.0.0", + "pupa": "^3.1.0", + "semver": "^7.6.2", + "semver-diff": "^4.0.0", + "xdg-basedir": "^5.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/yeoman/update-notifier?sponsor=1" + } + }, + "node_modules/update-notifier/node_modules/chalk": { + "version": "5.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/update-notifier/node_modules/is-installed-globally": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "global-directory": "^4.0.1", + "is-path-inside": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-notifier/node_modules/is-path-inside": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-notifier/node_modules/latest-version": { + "version": "9.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "package-json": "^10.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-notifier/node_modules/package-json": { + "version": "10.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ky": "^1.2.0", + "registry-auth-token": "^5.0.2", + "registry-url": "^6.0.1", + "semver": "^7.6.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/urix": { + "version": "0.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/url-regex": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-regex": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/use": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/uuid": { + "version": "8.3.2", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8flags": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/value-or-function": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl": { + "version": "2.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-fs": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "fs-mkdirp-stream": "^1.0.0", + "glob-stream": "^6.1.0", + "graceful-fs": "^4.0.0", + "is-valid-glob": "^1.0.0", + "lazystream": "^1.0.0", + "lead": "^1.0.0", + "object.assign": "^4.0.4", + "pumpify": "^1.3.5", + "readable-stream": "^2.3.3", + "remove-bom-buffer": "^3.0.0", + "remove-bom-stream": "^1.2.0", + "resolve-options": "^1.1.0", + "through2": "^2.0.0", + "to-through": "^2.0.0", + "value-or-function": "^3.0.0", + "vinyl": "^2.0.0", + "vinyl-sourcemap": "^1.1.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-fs/node_modules/isarray": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/vinyl-fs/node_modules/readable-stream": { + "version": "2.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/vinyl-fs/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/vinyl-fs/node_modules/string_decoder": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/vinyl-fs/node_modules/through2": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/vinyl-sourcemap": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "append-buffer": "^1.0.2", + "convert-source-map": "^1.5.0", + "graceful-fs": "^4.1.6", + "normalize-path": "^2.1.1", + "now-and-later": "^2.0.0", + "remove-bom-buffer": "^3.0.0", + "vinyl": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-sourcemap/node_modules/convert-source-map": { + "version": "1.9.0", + "dev": true, + "license": "MIT" + }, + "node_modules/vinyl-sourcemap/node_modules/normalize-path": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vinyl-sourcemaps-apply": { + "version": "0.2.1", + "dev": true, + "license": "ISC", + "dependencies": { + "source-map": "^0.5.1" + } + }, + "node_modules/vinyl-sourcemaps-apply/node_modules/source-map": { + "version": "0.5.7", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vinyl/node_modules/clone": { + "version": "2.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "1.3.1", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-module": { + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/widest-line": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/ansi-regex": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/widest-line/node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "license": "MIT" + }, + "node_modules/widest-line/node_modules/string-width": { + "version": "5.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/wildcard-match": { + "version": "5.1.3", + "dev": true, + "license": "ISC" + }, + "node_modules/windows-release": { + "version": "5.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.1.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "dev": true, + "license": "ISC" + }, + "node_modules/wrench-sui": { + "version": "0.0.3", + "dev": true, + "engines": { + "node": ">=0.1.97" + } + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/xdg-basedir": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.4.2", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yamljs": { + "version": "0.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "glob": "^7.0.5" + }, + "bin": { + "json2yaml": "bin/json2yaml", + "yaml2json": "bin/yaml2json" + } + }, + "node_modules/yamljs/node_modules/argparse": { + "version": "1.0.10", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..e7057a8 --- /dev/null +++ b/package.json @@ -0,0 +1,138 @@ +{ + "private": true, + "name": "codexgo", + "version": "4.6.1", + "description": "codexGO", + "author": "Bastean ", + "license": "MIT", + "homepage": "https://github.com/bastean/codexgo#readme", + "repository": { + "type": "git", + "url": "https://github.com/bastean/codexgo.git" + }, + "bugs": { + "url": "https://github.com/bastean/codexgo/issues" + }, + "engines": { + "node": ">=20", + "npm": ">=10" + }, + "workspaces": [ + "internal/app/*" + ], + "devDependencies": { + "@commitlint/cli": "19.3.0", + "@commitlint/config-conventional": "19.2.2", + "@release-it/bumper": "6.0.1", + "@release-it/conventional-changelog": "8.0.1", + "commitizen": "4.3.0", + "cz-conventional-changelog": "3.3.0", + "husky": "9.1.4", + "lint-staged": "15.2.7", + "npm-check-updates": "17.0.1", + "prettier": "3.3.3", + "release-it": "17.6.0" + }, + "lint-staged": { + "**/*": [ + "trufflehog filesystem --no-update", + "prettier --ignore-unknown --write" + ], + "**/*.go": [ + "gofmt -l -s -w" + ] + }, + "config": { + "commitizen": { + "path": "cz-conventional-changelog" + } + }, + "commitlint": { + "extends": [ + "@commitlint/config-conventional" + ] + }, + "release-it": { + "git": { + "requireBranch": "main", + "commitMessage": "chore(release): v${version}", + "tagAnnotation": "codexgo ${version}", + "tagName": "v${version}" + }, + "github": { + "release": true, + "releaseName": "v${version}" + }, + "plugins": { + "@release-it/conventional-changelog": { + "preset": { + "name": "conventionalcommits", + "types": [ + { + "type": "build", + "section": "Builds" + }, + { + "type": "chore", + "section": "Chores" + }, + { + "type": "ci", + "section": "Continuous Integration" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "feat", + "section": "New Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "perf", + "section": "Performance Improvements" + }, + { + "type": "refactor", + "section": "Refactors" + }, + { + "type": "revert", + "section": "Reverts" + }, + { + "type": "style", + "section": "Styles" + }, + { + "type": "test", + "section": "Tests" + } + ] + }, + "infile": "CHANGELOG.md", + "header": "# Changelog" + }, + "@release-it/bumper": { + "out": [ + "pkg/**/manifest.json" + ] + } + }, + "hooks": { + "before:init": [ + "make lint-check", + "make test-unit" + ], + "before:release": [ + "make lint", + "git add . --update" + ], + "after:release": "echo Successfully released ${name} v${version} to ${repo.repository}" + } + } +} diff --git a/pkg/context/shared/domain/aggregates/root.go b/pkg/context/shared/domain/aggregates/root.go new file mode 100644 index 0000000..a1e8bc2 --- /dev/null +++ b/pkg/context/shared/domain/aggregates/root.go @@ -0,0 +1,27 @@ +package aggregates + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages" +) + +type Root struct { + Messages []*messages.Message +} + +func (root *Root) RecordMessage(message *messages.Message) { + root.Messages = append(root.Messages, message) +} + +func (root *Root) PullMessages() []*messages.Message { + recordedMessages := root.Messages + + root.Messages = []*messages.Message{} + + return recordedMessages +} + +func NewRoot() *Root { + return &Root{ + Messages: []*messages.Message{}, + } +} diff --git a/pkg/context/shared/domain/errors/bubble.go b/pkg/context/shared/domain/errors/bubble.go new file mode 100644 index 0000000..d93008d --- /dev/null +++ b/pkg/context/shared/domain/errors/bubble.go @@ -0,0 +1,52 @@ +package errors + +import ( + "encoding/json" + "fmt" + "time" +) + +type Bubble struct { + When time.Time + Where, What string + Why map[string]any + Who error +} + +func (err *Bubble) Error() string { + message := fmt.Sprintf("%s (%s): %s", err.When.Format(time.RFC3339Nano), err.Where, err.What) + + if err.Why != nil { + why, err := json.Marshal(err.Why) + + if err != nil { + Panic(fmt.Sprintf("cannot json encoding \"why\" from error bubble: %s: [%s]", message, err.Error()), "Error") + } + + message = fmt.Sprintf("%s: %s", message, why) + } + + if err.Who != nil { + message = fmt.Sprintf("%s: [%s]", message, err.Who) + } + + return message +} + +func NewBubble(where, what string, why Meta, who error) *Bubble { + if where == "" { + Panic("cannot create a error bubble if \"where\" is not defined", "NewBubble") + } + + if what == "" { + Panic("cannot create a error bubble if \"what\" is not defined", "NewBubble") + } + + return &Bubble{ + When: time.Now().UTC(), + Where: where, + What: what, + Why: why, + Who: who, + } +} diff --git a/pkg/context/shared/domain/errors/bubbleup.go b/pkg/context/shared/domain/errors/bubbleup.go new file mode 100644 index 0000000..15e5415 --- /dev/null +++ b/pkg/context/shared/domain/errors/bubbleup.go @@ -0,0 +1,9 @@ +package errors + +import ( + "fmt" +) + +func BubbleUp(who error, where string) error { + return fmt.Errorf("(%s): [%w]", where, who) +} diff --git a/pkg/context/shared/domain/errors/default.go b/pkg/context/shared/domain/errors/default.go new file mode 100644 index 0000000..c6a2d0f --- /dev/null +++ b/pkg/context/shared/domain/errors/default.go @@ -0,0 +1,5 @@ +package errors + +func Default() error { + return new(Bubble) +} diff --git a/pkg/context/shared/domain/errors/exist.go b/pkg/context/shared/domain/errors/exist.go new file mode 100644 index 0000000..641a17d --- /dev/null +++ b/pkg/context/shared/domain/errors/exist.go @@ -0,0 +1,31 @@ +package errors + +type ErrAlreadyExist struct { + *Bubble +} + +type ErrNotExist struct { + *Bubble +} + +func NewAlreadyExist(bubble *Bubble) error { + return &ErrAlreadyExist{ + Bubble: NewBubble( + bubble.Where, + bubble.What, + bubble.Why, + bubble.Who, + ), + } +} + +func NewNotExist(bubble *Bubble) error { + return &ErrNotExist{ + Bubble: NewBubble( + bubble.Where, + bubble.What, + bubble.Why, + bubble.Who, + ), + } +} diff --git a/pkg/context/shared/domain/errors/failure.go b/pkg/context/shared/domain/errors/failure.go new file mode 100644 index 0000000..1dfc23d --- /dev/null +++ b/pkg/context/shared/domain/errors/failure.go @@ -0,0 +1,16 @@ +package errors + +type ErrFailure struct { + *Bubble +} + +func NewFailure(bubble *Bubble) error { + return &ErrFailure{ + Bubble: NewBubble( + bubble.Where, + bubble.What, + bubble.Why, + bubble.Who, + ), + } +} diff --git a/pkg/context/shared/domain/errors/internal.go b/pkg/context/shared/domain/errors/internal.go new file mode 100644 index 0000000..4a6fb42 --- /dev/null +++ b/pkg/context/shared/domain/errors/internal.go @@ -0,0 +1,16 @@ +package errors + +type ErrInternal struct { + *Bubble +} + +func NewInternal(bubble *Bubble) error { + return &ErrInternal{ + Bubble: NewBubble( + bubble.Where, + bubble.What, + bubble.Why, + bubble.Who, + ), + } +} diff --git a/pkg/context/shared/domain/errors/invalid.go b/pkg/context/shared/domain/errors/invalid.go new file mode 100644 index 0000000..d0735cd --- /dev/null +++ b/pkg/context/shared/domain/errors/invalid.go @@ -0,0 +1,16 @@ +package errors + +type ErrInvalidValue struct { + *Bubble +} + +func NewInvalidValue(bubble *Bubble) error { + return &ErrInvalidValue{ + Bubble: NewBubble( + bubble.Where, + bubble.What, + bubble.Why, + bubble.Who, + ), + } +} diff --git a/pkg/context/shared/domain/errors/meta.go b/pkg/context/shared/domain/errors/meta.go new file mode 100644 index 0000000..7033e59 --- /dev/null +++ b/pkg/context/shared/domain/errors/meta.go @@ -0,0 +1,3 @@ +package errors + +type Meta = map[string]any diff --git a/pkg/context/shared/domain/errors/panic.go b/pkg/context/shared/domain/errors/panic.go new file mode 100644 index 0000000..965b572 --- /dev/null +++ b/pkg/context/shared/domain/errors/panic.go @@ -0,0 +1,9 @@ +package errors + +import ( + "log" +) + +func Panic(what, where string) { + log.Panicf("(%s): %s", where, what) +} diff --git a/pkg/context/shared/domain/errors/wrap.go b/pkg/context/shared/domain/errors/wrap.go new file mode 100644 index 0000000..708aae4 --- /dev/null +++ b/pkg/context/shared/domain/errors/wrap.go @@ -0,0 +1,17 @@ +package errors + +import ( + "errors" +) + +func Join(errs ...error) error { + return errors.Join(errs...) +} + +func As(err error, target any) bool { + return errors.As(err, target) +} + +func Is(err, target error) bool { + return errors.Is(err, target) +} diff --git a/pkg/context/shared/domain/handlers/command.go b/pkg/context/shared/domain/handlers/command.go new file mode 100644 index 0000000..c0c7fff --- /dev/null +++ b/pkg/context/shared/domain/handlers/command.go @@ -0,0 +1,5 @@ +package handlers + +type Command[Command any] interface { + Handle(Command) error +} diff --git a/pkg/context/shared/domain/handlers/query.go b/pkg/context/shared/domain/handlers/query.go new file mode 100644 index 0000000..306fdec --- /dev/null +++ b/pkg/context/shared/domain/handlers/query.go @@ -0,0 +1,5 @@ +package handlers + +type Query[Query, Response any] interface { + Handle(Query) (Response, error) +} diff --git a/pkg/context/shared/domain/loggers/logger.go b/pkg/context/shared/domain/loggers/logger.go new file mode 100644 index 0000000..5dfafd7 --- /dev/null +++ b/pkg/context/shared/domain/loggers/logger.go @@ -0,0 +1,9 @@ +package loggers + +type Logger interface { + Debug(string) + Error(string) + Fatal(string) + Info(string) + Success(string) +} diff --git a/pkg/context/shared/domain/messages/broker.go b/pkg/context/shared/domain/messages/broker.go new file mode 100644 index 0000000..f6386ac --- /dev/null +++ b/pkg/context/shared/domain/messages/broker.go @@ -0,0 +1,9 @@ +package messages + +type Broker interface { + AddRouter(*Router) error + AddQueue(*Queue) error + AddQueueMessageBind(*Queue, BindingKeys) error + AddQueueConsumer(Consumer) error + PublishMessages([]*Message) error +} diff --git a/pkg/context/shared/domain/messages/consumer.go b/pkg/context/shared/domain/messages/consumer.go new file mode 100644 index 0000000..60865d2 --- /dev/null +++ b/pkg/context/shared/domain/messages/consumer.go @@ -0,0 +1,6 @@ +package messages + +type Consumer interface { + SubscribedTo() []*Queue + On(*Message) error +} diff --git a/pkg/context/shared/domain/messages/key.go b/pkg/context/shared/domain/messages/key.go new file mode 100644 index 0000000..9e4ed19 --- /dev/null +++ b/pkg/context/shared/domain/messages/key.go @@ -0,0 +1,76 @@ +package messages + +import ( + "fmt" + "strings" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages/valueobjs" +) + +var Type = struct { + Event, Command string +}{ + Event: "event", + Command: "command", +} + +var Status = struct { + Queued, Succeeded, Failed, Done string +}{ + Queued: "queued", + Succeeded: "succeeded", + Failed: "failed", + Done: "done", +} + +// Terminology: +// - Organization = Context +// - Service = Module +// - Entity = Aggregate/Root +// +// Nomenclature of a Routing Key (Topic): +// - organization.service.version.type.entity.event/command.status +// - codexgo.user.1.event.user.created.succeeded +type RoutingKeyComponents struct { + Organization, Service, Version, Type, Entity, Event, Command, Status string +} + +func NewRoutingKey(components *RoutingKeyComponents) string { + if components.Organization == "" { + components.Organization = "codexgo" + } + + organization, errOrganization := valueobjs.NewOrganization(components.Organization) + service, errService := valueobjs.NewService(components.Service) + version, errVersion := valueobjs.NewVersion(components.Version) + types, errType := valueobjs.NewType(components.Type) + entity, errEntity := valueobjs.NewEntity(components.Entity) + + event, errEvent := valueobjs.NewEvent(components.Event) + command, errCommand := valueobjs.NewCommand(components.Command) + + var action string + var errAction error + + switch components.Type { + case Type.Event: + action = event.Value + errAction = errEvent + case Type.Command: + action = command.Value + errAction = errCommand + } + + status, errStatus := valueobjs.NewStatus(components.Status) + + if err := errors.Join(errOrganization, errService, errVersion, errType, errEntity, errAction, errStatus); err != nil { + errors.Panic(err.Error(), "NewRoutingKey") + } + + key := fmt.Sprintf("%s.%s.%s.%s.%s.%s.%s", organization.Value, service.Value, version.Value, types.Value, entity.Value, action, status.Value) + + key = strings.ToLower(key) + + return key +} diff --git a/pkg/context/shared/domain/messages/key_test.go b/pkg/context/shared/domain/messages/key_test.go new file mode 100644 index 0000000..f1335d9 --- /dev/null +++ b/pkg/context/shared/domain/messages/key_test.go @@ -0,0 +1,41 @@ +package messages_test + +import ( + "testing" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages" + "github.com/stretchr/testify/suite" +) + +type RoutingKeyTestSuite struct { + suite.Suite +} + +func (suite *RoutingKeyTestSuite) SetupTest() {} + +func (suite *RoutingKeyTestSuite) TestWithValidValue() { + components := &messages.RoutingKeyComponents{ + Organization: "codexgo", + Service: "user", + Version: "1", + Type: messages.Type.Event, + Entity: "user", + Event: "created", + Status: messages.Status.Succeeded, + } + + expected := "codexgo.user.1.event.user.created.succeeded" + + actual := messages.NewRoutingKey(components) + + suite.Equal(expected, actual) +} + +func (suite *RoutingKeyTestSuite) TestWithInvalidValue() { + components := &messages.RoutingKeyComponents{} + suite.Panics(func() { messages.NewRoutingKey(components) }) +} + +func TestUnitRoutingKeySuite(t *testing.T) { + suite.Run(t, new(RoutingKeyTestSuite)) +} diff --git a/pkg/context/shared/domain/messages/message.go b/pkg/context/shared/domain/messages/message.go new file mode 100644 index 0000000..9cae5ff --- /dev/null +++ b/pkg/context/shared/domain/messages/message.go @@ -0,0 +1,18 @@ +package messages + +type Attributes = []byte + +type Meta = []byte + +type Message struct { + Id, Type, OccurredOn string + Attributes, Meta []byte +} + +func New(routingKey string, attributes, meta []byte) *Message { + return &Message{ + Type: routingKey, + Attributes: attributes, + Meta: meta, + } +} diff --git a/pkg/context/shared/domain/messages/queue.go b/pkg/context/shared/domain/messages/queue.go new file mode 100644 index 0000000..3217222 --- /dev/null +++ b/pkg/context/shared/domain/messages/queue.go @@ -0,0 +1,21 @@ +package messages + +type BindingKeys = []string + +type Queue struct { + Name string + Bindings BindingKeys +} + +func HasNoQueue(queues []Queue, queue *Queue) bool { + isNotPresent := true + + for _, present := range queues { + if present.Name == queue.Name { + isNotPresent = false + break + } + } + + return isNotPresent +} diff --git a/pkg/context/shared/domain/messages/recipient.go b/pkg/context/shared/domain/messages/recipient.go new file mode 100644 index 0000000..62e2dc1 --- /dev/null +++ b/pkg/context/shared/domain/messages/recipient.go @@ -0,0 +1,53 @@ +package messages + +import ( + "fmt" + "strings" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages/valueobjs" +) + +// Terminology: +// - Service = Module +// - Entity = Aggregate/Root +// +// Nomenclature of a Recipient Name: +// - service.entity.action_on_event/command_status +// - user.user.send_confirmation_on_created_succeeded +type RecipientNameComponents struct { + Service, Entity, Action, Event, Command, Status string +} + +func NewRecipientName(components *RecipientNameComponents) string { + service, errService := valueobjs.NewService(components.Service) + entity, errEntity := valueobjs.NewEntity(components.Entity) + action, errAction := valueobjs.NewAction(components.Action) + + event, errEvent := valueobjs.NewEvent(components.Event) + command, errCommand := valueobjs.NewCommand(components.Command) + + var trigger string + var errTrigger error + + switch { + case components.Event != "": + trigger = event.Value + errTrigger = errEvent + case components.Command != "": + trigger = command.Value + errTrigger = errCommand + } + + status, errStatus := valueobjs.NewStatus(components.Status) + + if err := errors.Join(errService, errEntity, errAction, errTrigger, errStatus); err != nil { + errors.Panic(err.Error(), "NewRecipientName") + } + + name := fmt.Sprintf("%s.%s.%s_on_%s_%s", service.Value, entity.Value, strings.ReplaceAll(action.Value, " ", "_"), trigger, status.Value) + + name = strings.ToLower(name) + + return name +} diff --git a/pkg/context/shared/domain/messages/recipient_test.go b/pkg/context/shared/domain/messages/recipient_test.go new file mode 100644 index 0000000..b51ca7f --- /dev/null +++ b/pkg/context/shared/domain/messages/recipient_test.go @@ -0,0 +1,39 @@ +package messages_test + +import ( + "testing" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages" + "github.com/stretchr/testify/suite" +) + +type RecipientNameTestSuite struct { + suite.Suite +} + +func (suite *RecipientNameTestSuite) SetupTest() {} + +func (suite *RecipientNameTestSuite) TestWithValidValue() { + components := &messages.RecipientNameComponents{ + Service: "user", + Entity: "user", + Action: "send confirmation", + Event: "created", + Status: messages.Status.Succeeded, + } + + expected := "user.user.send_confirmation_on_created_succeeded" + + actual := messages.NewRecipientName(components) + + suite.Equal(expected, actual) +} + +func (suite *RecipientNameTestSuite) TestWithInvalidValue() { + components := &messages.RecipientNameComponents{} + suite.Panics(func() { messages.NewRecipientName(components) }) +} + +func TestUnitRecipientNameSuite(t *testing.T) { + suite.Run(t, new(RecipientNameTestSuite)) +} diff --git a/pkg/context/shared/domain/messages/router.go b/pkg/context/shared/domain/messages/router.go new file mode 100644 index 0000000..95c52d2 --- /dev/null +++ b/pkg/context/shared/domain/messages/router.go @@ -0,0 +1,5 @@ +package messages + +type Router struct { + Name string +} diff --git a/pkg/context/shared/domain/messages/valueobjs/action.go b/pkg/context/shared/domain/messages/valueobjs/action.go new file mode 100644 index 0000000..2d206fe --- /dev/null +++ b/pkg/context/shared/domain/messages/valueobjs/action.go @@ -0,0 +1,36 @@ +package valueobjs + +import ( + "fmt" + "strings" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/services" +) + +const ActionMinCharactersLength = "1" +const ActionMaxCharactersLength = "20" + +type Action struct { + Value string `validate:"gte=1,lte=20"` +} + +func NewAction(value string) (*Action, error) { + value = strings.TrimSpace(value) + + valueObj := &Action{ + Value: value, + } + + if services.IsValueObjectInvalid(valueObj) { + return nil, errors.NewInvalidValue(&errors.Bubble{ + Where: "NewAction", + What: fmt.Sprintf("Action must be between %s to %s characters", ActionMinCharactersLength, ActionMaxCharactersLength), + Why: errors.Meta{ + "Action": value, + }, + }) + } + + return valueObj, nil +} diff --git a/pkg/context/shared/domain/messages/valueobjs/action.mother.go b/pkg/context/shared/domain/messages/valueobjs/action.mother.go new file mode 100644 index 0000000..6251e7a --- /dev/null +++ b/pkg/context/shared/domain/messages/valueobjs/action.mother.go @@ -0,0 +1,24 @@ +package valueobjs + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/services" +) + +func ActionWithValidValue() *Action { + value, err := NewAction(services.Create.Regex(`^[A-Za-z\s]{1,20}$`)) + + if err != nil { + errors.Panic(err.Error(), "ActionWithValidValue") + } + + return value +} + +func ActionWithInvalidLength() (string, error) { + value := "" + + _, err := NewAction(value) + + return value, err +} diff --git a/pkg/context/shared/domain/messages/valueobjs/action_test.go b/pkg/context/shared/domain/messages/valueobjs/action_test.go new file mode 100644 index 0000000..23d6b95 --- /dev/null +++ b/pkg/context/shared/domain/messages/valueobjs/action_test.go @@ -0,0 +1,38 @@ +package valueobjs_test + +import ( + "testing" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages/valueobjs" + "github.com/stretchr/testify/suite" +) + +type ActionValueObjectTestSuite struct { + suite.Suite +} + +func (suite *ActionValueObjectTestSuite) SetupTest() {} + +func (suite *ActionValueObjectTestSuite) TestWithInvalidLength() { + value, err := valueobjs.ActionWithInvalidLength() + + var actual *errors.ErrInvalidValue + + suite.ErrorAs(err, &actual) + + expected := &errors.ErrInvalidValue{Bubble: &errors.Bubble{ + When: actual.When, + Where: "NewAction", + What: "Action must be between 1 to 20 characters", + Why: errors.Meta{ + "Action": value, + }, + }} + + suite.EqualError(expected, actual.Error()) +} + +func TestUnitActionValueObjectSuite(t *testing.T) { + suite.Run(t, new(ActionValueObjectTestSuite)) +} diff --git a/pkg/context/shared/domain/messages/valueobjs/command.go b/pkg/context/shared/domain/messages/valueobjs/command.go new file mode 100644 index 0000000..db1629c --- /dev/null +++ b/pkg/context/shared/domain/messages/valueobjs/command.go @@ -0,0 +1,36 @@ +package valueobjs + +import ( + "fmt" + "strings" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/services" +) + +const CommandMinCharactersLength = "1" +const CommandMaxCharactersLength = "20" + +type Command struct { + Value string `validate:"gte=1,lte=20,alpha"` +} + +func NewCommand(value string) (*Command, error) { + value = strings.TrimSpace(value) + + valueObj := &Command{ + Value: value, + } + + if services.IsValueObjectInvalid(valueObj) { + return nil, errors.NewInvalidValue(&errors.Bubble{ + Where: "NewCommand", + What: fmt.Sprintf("Command must be between %s to %s characters and be alpha only", CommandMinCharactersLength, CommandMaxCharactersLength), + Why: errors.Meta{ + "Command": value, + }, + }) + } + + return valueObj, nil +} diff --git a/pkg/context/shared/domain/messages/valueobjs/command.mother.go b/pkg/context/shared/domain/messages/valueobjs/command.mother.go new file mode 100644 index 0000000..57cdfd5 --- /dev/null +++ b/pkg/context/shared/domain/messages/valueobjs/command.mother.go @@ -0,0 +1,32 @@ +package valueobjs + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/services" +) + +func CommandWithValidValue() *Command { + value, err := NewCommand(services.Create.Regex(`^[A-Za-z]{1,20}$`)) + + if err != nil { + errors.Panic(err.Error(), "CommandWithValidValue") + } + + return value +} + +func CommandWithInvalidLength() (string, error) { + value := "" + + _, err := NewCommand(value) + + return value, err +} + +func CommandWithInvalidAlpha() (string, error) { + value := "<>" + + _, err := NewCommand(value) + + return value, err +} diff --git a/pkg/context/shared/domain/messages/valueobjs/command_test.go b/pkg/context/shared/domain/messages/valueobjs/command_test.go new file mode 100644 index 0000000..85026c1 --- /dev/null +++ b/pkg/context/shared/domain/messages/valueobjs/command_test.go @@ -0,0 +1,57 @@ +package valueobjs_test + +import ( + "testing" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages/valueobjs" + "github.com/stretchr/testify/suite" +) + +type CommandValueObjectTestSuite struct { + suite.Suite +} + +func (suite *CommandValueObjectTestSuite) SetupTest() {} + +func (suite *CommandValueObjectTestSuite) TestWithInvalidLength() { + value, err := valueobjs.CommandWithInvalidLength() + + var actual *errors.ErrInvalidValue + + suite.ErrorAs(err, &actual) + + expected := &errors.ErrInvalidValue{Bubble: &errors.Bubble{ + When: actual.When, + Where: "NewCommand", + What: "Command must be between 1 to 20 characters and be alpha only", + Why: errors.Meta{ + "Command": value, + }, + }} + + suite.EqualError(expected, actual.Error()) +} + +func (suite *CommandValueObjectTestSuite) TestWithInvalidAlpha() { + value, err := valueobjs.CommandWithInvalidAlpha() + + var actual *errors.ErrInvalidValue + + suite.ErrorAs(err, &actual) + + expected := &errors.ErrInvalidValue{Bubble: &errors.Bubble{ + When: actual.When, + Where: "NewCommand", + What: "Command must be between 1 to 20 characters and be alpha only", + Why: errors.Meta{ + "Command": value, + }, + }} + + suite.EqualError(expected, actual.Error()) +} + +func TestUnitCommandValueObjectSuite(t *testing.T) { + suite.Run(t, new(CommandValueObjectTestSuite)) +} diff --git a/pkg/context/shared/domain/messages/valueobjs/entity.go b/pkg/context/shared/domain/messages/valueobjs/entity.go new file mode 100644 index 0000000..7dbfccd --- /dev/null +++ b/pkg/context/shared/domain/messages/valueobjs/entity.go @@ -0,0 +1,36 @@ +package valueobjs + +import ( + "fmt" + "strings" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/services" +) + +const EntityMinCharactersLength = "1" +const EntityMaxCharactersLength = "20" + +type Entity struct { + Value string `validate:"gte=1,lte=20,alpha"` +} + +func NewEntity(value string) (*Entity, error) { + value = strings.TrimSpace(value) + + valueObj := &Entity{ + Value: value, + } + + if services.IsValueObjectInvalid(valueObj) { + return nil, errors.NewInvalidValue(&errors.Bubble{ + Where: "NewEntity", + What: fmt.Sprintf("Entity must be between %s to %s characters and be alpha only", EntityMinCharactersLength, EntityMaxCharactersLength), + Why: errors.Meta{ + "Entity": value, + }, + }) + } + + return valueObj, nil +} diff --git a/pkg/context/shared/domain/messages/valueobjs/entity.mother.go b/pkg/context/shared/domain/messages/valueobjs/entity.mother.go new file mode 100644 index 0000000..37f749a --- /dev/null +++ b/pkg/context/shared/domain/messages/valueobjs/entity.mother.go @@ -0,0 +1,32 @@ +package valueobjs + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/services" +) + +func EntityWithValidValue() *Entity { + value, err := NewEntity(services.Create.Regex(`^[A-Za-z]{1,20}$`)) + + if err != nil { + errors.Panic(err.Error(), "EntityWithValidValue") + } + + return value +} + +func EntityWithInvalidLength() (string, error) { + value := "" + + _, err := NewEntity(value) + + return value, err +} + +func EntityWithInvalidAlpha() (string, error) { + value := "<>" + + _, err := NewEntity(value) + + return value, err +} diff --git a/pkg/context/shared/domain/messages/valueobjs/entity_test.go b/pkg/context/shared/domain/messages/valueobjs/entity_test.go new file mode 100644 index 0000000..c2327c1 --- /dev/null +++ b/pkg/context/shared/domain/messages/valueobjs/entity_test.go @@ -0,0 +1,57 @@ +package valueobjs_test + +import ( + "testing" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages/valueobjs" + "github.com/stretchr/testify/suite" +) + +type EntityValueObjectTestSuite struct { + suite.Suite +} + +func (suite *EntityValueObjectTestSuite) SetupTest() {} + +func (suite *EntityValueObjectTestSuite) TestWithInvalidLength() { + value, err := valueobjs.EntityWithInvalidLength() + + var actual *errors.ErrInvalidValue + + suite.ErrorAs(err, &actual) + + expected := &errors.ErrInvalidValue{Bubble: &errors.Bubble{ + When: actual.When, + Where: "NewEntity", + What: "Entity must be between 1 to 20 characters and be alpha only", + Why: errors.Meta{ + "Entity": value, + }, + }} + + suite.EqualError(expected, actual.Error()) +} + +func (suite *EntityValueObjectTestSuite) TestWithInvalidAlpha() { + value, err := valueobjs.EntityWithInvalidAlpha() + + var actual *errors.ErrInvalidValue + + suite.ErrorAs(err, &actual) + + expected := &errors.ErrInvalidValue{Bubble: &errors.Bubble{ + When: actual.When, + Where: "NewEntity", + What: "Entity must be between 1 to 20 characters and be alpha only", + Why: errors.Meta{ + "Entity": value, + }, + }} + + suite.EqualError(expected, actual.Error()) +} + +func TestUnitEntityValueObjectSuite(t *testing.T) { + suite.Run(t, new(EntityValueObjectTestSuite)) +} diff --git a/pkg/context/shared/domain/messages/valueobjs/event.go b/pkg/context/shared/domain/messages/valueobjs/event.go new file mode 100644 index 0000000..6c2baf7 --- /dev/null +++ b/pkg/context/shared/domain/messages/valueobjs/event.go @@ -0,0 +1,36 @@ +package valueobjs + +import ( + "fmt" + "strings" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/services" +) + +const EventMinCharactersLength = "1" +const EventMaxCharactersLength = "20" + +type Event struct { + Value string `validate:"gte=1,lte=20,alpha"` +} + +func NewEvent(value string) (*Event, error) { + value = strings.TrimSpace(value) + + valueObj := &Event{ + Value: value, + } + + if services.IsValueObjectInvalid(valueObj) { + return nil, errors.NewInvalidValue(&errors.Bubble{ + Where: "NewEvent", + What: fmt.Sprintf("Event must be between %s to %s characters and be alpha only", EventMinCharactersLength, EventMaxCharactersLength), + Why: errors.Meta{ + "Event": value, + }, + }) + } + + return valueObj, nil +} diff --git a/pkg/context/shared/domain/messages/valueobjs/event.mother.go b/pkg/context/shared/domain/messages/valueobjs/event.mother.go new file mode 100644 index 0000000..aedaf75 --- /dev/null +++ b/pkg/context/shared/domain/messages/valueobjs/event.mother.go @@ -0,0 +1,32 @@ +package valueobjs + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/services" +) + +func EventWithValidValue() *Event { + value, err := NewEvent(services.Create.Regex(`^[A-Za-z]{1,20}$`)) + + if err != nil { + errors.Panic(err.Error(), "EventWithValidValue") + } + + return value +} + +func EventWithInvalidLength() (string, error) { + value := "" + + _, err := NewEvent(value) + + return value, err +} + +func EventWithInvalidAlpha() (string, error) { + value := "<>" + + _, err := NewEvent(value) + + return value, err +} diff --git a/pkg/context/shared/domain/messages/valueobjs/event_test.go b/pkg/context/shared/domain/messages/valueobjs/event_test.go new file mode 100644 index 0000000..f2e723a --- /dev/null +++ b/pkg/context/shared/domain/messages/valueobjs/event_test.go @@ -0,0 +1,57 @@ +package valueobjs_test + +import ( + "testing" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages/valueobjs" + "github.com/stretchr/testify/suite" +) + +type EventValueObjectTestSuite struct { + suite.Suite +} + +func (suite *EventValueObjectTestSuite) SetupTest() {} + +func (suite *EventValueObjectTestSuite) TestWithInvalidLength() { + value, err := valueobjs.EventWithInvalidLength() + + var actual *errors.ErrInvalidValue + + suite.ErrorAs(err, &actual) + + expected := &errors.ErrInvalidValue{Bubble: &errors.Bubble{ + When: actual.When, + Where: "NewEvent", + What: "Event must be between 1 to 20 characters and be alpha only", + Why: errors.Meta{ + "Event": value, + }, + }} + + suite.EqualError(expected, actual.Error()) +} + +func (suite *EventValueObjectTestSuite) TestWithInvalidAlpha() { + value, err := valueobjs.EventWithInvalidAlpha() + + var actual *errors.ErrInvalidValue + + suite.ErrorAs(err, &actual) + + expected := &errors.ErrInvalidValue{Bubble: &errors.Bubble{ + When: actual.When, + Where: "NewEvent", + What: "Event must be between 1 to 20 characters and be alpha only", + Why: errors.Meta{ + "Event": value, + }, + }} + + suite.EqualError(expected, actual.Error()) +} + +func TestUnitEventValueObjectSuite(t *testing.T) { + suite.Run(t, new(EventValueObjectTestSuite)) +} diff --git a/pkg/context/shared/domain/messages/valueobjs/organization.go b/pkg/context/shared/domain/messages/valueobjs/organization.go new file mode 100644 index 0000000..4ed6df7 --- /dev/null +++ b/pkg/context/shared/domain/messages/valueobjs/organization.go @@ -0,0 +1,36 @@ +package valueobjs + +import ( + "fmt" + "strings" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/services" +) + +const OrganizationMinCharactersLength = "1" +const OrganizationMaxCharactersLength = "20" + +type Organization struct { + Value string `validate:"gte=1,lte=20,alphanum"` +} + +func NewOrganization(value string) (*Organization, error) { + value = strings.TrimSpace(value) + + valueObj := &Organization{ + Value: value, + } + + if services.IsValueObjectInvalid(valueObj) { + return nil, errors.NewInvalidValue(&errors.Bubble{ + Where: "NewOrganization", + What: fmt.Sprintf("Organization must be between %s to %s characters and be alphanumeric only", OrganizationMinCharactersLength, OrganizationMaxCharactersLength), + Why: errors.Meta{ + "Organization": value, + }, + }) + } + + return valueObj, nil +} diff --git a/pkg/context/shared/domain/messages/valueobjs/organization.mother.go b/pkg/context/shared/domain/messages/valueobjs/organization.mother.go new file mode 100644 index 0000000..65b1cf9 --- /dev/null +++ b/pkg/context/shared/domain/messages/valueobjs/organization.mother.go @@ -0,0 +1,32 @@ +package valueobjs + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/services" +) + +func OrganizationWithValidValue() *Organization { + value, err := NewOrganization(services.Create.Regex(`^[A-Za-z0-9]{1,20}$`)) + + if err != nil { + errors.Panic(err.Error(), "OrganizationWithValidValue") + } + + return value +} + +func OrganizationWithInvalidLength() (string, error) { + value := "" + + _, err := NewOrganization(value) + + return value, err +} + +func OrganizationWithInvalidAlphanumeric() (string, error) { + value := "<>" + + _, err := NewOrganization(value) + + return value, err +} diff --git a/pkg/context/shared/domain/messages/valueobjs/organization_test.go b/pkg/context/shared/domain/messages/valueobjs/organization_test.go new file mode 100644 index 0000000..49f8698 --- /dev/null +++ b/pkg/context/shared/domain/messages/valueobjs/organization_test.go @@ -0,0 +1,57 @@ +package valueobjs_test + +import ( + "testing" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages/valueobjs" + "github.com/stretchr/testify/suite" +) + +type OrganizationValueObjectTestSuite struct { + suite.Suite +} + +func (suite *OrganizationValueObjectTestSuite) SetupTest() {} + +func (suite *OrganizationValueObjectTestSuite) TestWithInvalidLength() { + value, err := valueobjs.OrganizationWithInvalidLength() + + var actual *errors.ErrInvalidValue + + suite.ErrorAs(err, &actual) + + expected := &errors.ErrInvalidValue{Bubble: &errors.Bubble{ + When: actual.When, + Where: "NewOrganization", + What: "Organization must be between 1 to 20 characters and be alphanumeric only", + Why: errors.Meta{ + "Organization": value, + }, + }} + + suite.EqualError(expected, actual.Error()) +} + +func (suite *OrganizationValueObjectTestSuite) TestWithInvalidAlphanumeric() { + value, err := valueobjs.OrganizationWithInvalidAlphanumeric() + + var actual *errors.ErrInvalidValue + + suite.ErrorAs(err, &actual) + + expected := &errors.ErrInvalidValue{Bubble: &errors.Bubble{ + When: actual.When, + Where: "NewOrganization", + What: "Organization must be between 1 to 20 characters and be alphanumeric only", + Why: errors.Meta{ + "Organization": value, + }, + }} + + suite.EqualError(expected, actual.Error()) +} + +func TestUnitOrganizationValueObjectSuite(t *testing.T) { + suite.Run(t, new(OrganizationValueObjectTestSuite)) +} diff --git a/pkg/context/shared/domain/messages/valueobjs/service.go b/pkg/context/shared/domain/messages/valueobjs/service.go new file mode 100644 index 0000000..27130cf --- /dev/null +++ b/pkg/context/shared/domain/messages/valueobjs/service.go @@ -0,0 +1,36 @@ +package valueobjs + +import ( + "fmt" + "strings" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/services" +) + +const ServiceMinCharactersLength = "1" +const ServiceMaxCharactersLength = "20" + +type Service struct { + Value string `validate:"gte=1,lte=20,alphanum"` +} + +func NewService(value string) (*Service, error) { + value = strings.TrimSpace(value) + + valueObj := &Service{ + Value: value, + } + + if services.IsValueObjectInvalid(valueObj) { + return nil, errors.NewInvalidValue(&errors.Bubble{ + Where: "NewService", + What: fmt.Sprintf("Service must be between %s to %s characters and be alphanumeric only", ServiceMinCharactersLength, ServiceMaxCharactersLength), + Why: errors.Meta{ + "Service": value, + }, + }) + } + + return valueObj, nil +} diff --git a/pkg/context/shared/domain/messages/valueobjs/service.mother.go b/pkg/context/shared/domain/messages/valueobjs/service.mother.go new file mode 100644 index 0000000..50a600d --- /dev/null +++ b/pkg/context/shared/domain/messages/valueobjs/service.mother.go @@ -0,0 +1,32 @@ +package valueobjs + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/services" +) + +func ServiceWithValidValue() *Service { + value, err := NewService(services.Create.Regex(`^[A-Za-z0-9]{1,20}$`)) + + if err != nil { + errors.Panic(err.Error(), "ServiceWithValidValue") + } + + return value +} + +func ServiceWithInvalidLength() (string, error) { + value := "" + + _, err := NewService(value) + + return value, err +} + +func ServiceWithInvalidAlphanumeric() (string, error) { + value := "<>" + + _, err := NewService(value) + + return value, err +} diff --git a/pkg/context/shared/domain/messages/valueobjs/service_test.go b/pkg/context/shared/domain/messages/valueobjs/service_test.go new file mode 100644 index 0000000..f29b470 --- /dev/null +++ b/pkg/context/shared/domain/messages/valueobjs/service_test.go @@ -0,0 +1,57 @@ +package valueobjs_test + +import ( + "testing" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages/valueobjs" + "github.com/stretchr/testify/suite" +) + +type ServiceValueObjectTestSuite struct { + suite.Suite +} + +func (suite *ServiceValueObjectTestSuite) SetupTest() {} + +func (suite *ServiceValueObjectTestSuite) TestWithInvalidLength() { + value, err := valueobjs.ServiceWithInvalidLength() + + var actual *errors.ErrInvalidValue + + suite.ErrorAs(err, &actual) + + expected := &errors.ErrInvalidValue{Bubble: &errors.Bubble{ + When: actual.When, + Where: "NewService", + What: "Service must be between 1 to 20 characters and be alphanumeric only", + Why: errors.Meta{ + "Service": value, + }, + }} + + suite.EqualError(expected, actual.Error()) +} + +func (suite *ServiceValueObjectTestSuite) TestWithInvalidAlphanumeric() { + value, err := valueobjs.ServiceWithInvalidAlphanumeric() + + var actual *errors.ErrInvalidValue + + suite.ErrorAs(err, &actual) + + expected := &errors.ErrInvalidValue{Bubble: &errors.Bubble{ + When: actual.When, + Where: "NewService", + What: "Service must be between 1 to 20 characters and be alphanumeric only", + Why: errors.Meta{ + "Service": value, + }, + }} + + suite.EqualError(expected, actual.Error()) +} + +func TestUnitServiceValueObjectSuite(t *testing.T) { + suite.Run(t, new(ServiceValueObjectTestSuite)) +} diff --git a/pkg/context/shared/domain/messages/valueobjs/status.go b/pkg/context/shared/domain/messages/valueobjs/status.go new file mode 100644 index 0000000..73f5cf2 --- /dev/null +++ b/pkg/context/shared/domain/messages/valueobjs/status.go @@ -0,0 +1,37 @@ +package valueobjs + +import ( + "fmt" + "strings" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/services" +) + +var StatusOneOf = []string{"Queued", "Succeeded", "Failed", "Done"} + +type Status struct { + Value string `validate:"oneof=queued succeeded failed done"` +} + +func NewStatus(value string) (*Status, error) { + value = strings.TrimSpace(value) + + value = strings.ToLower(value) + + valueObj := &Status{ + Value: value, + } + + if services.IsValueObjectInvalid(valueObj) { + return nil, errors.NewInvalidValue(&errors.Bubble{ + Where: "NewStatus", + What: fmt.Sprintf("Status must be only one of these values: %s", strings.Join(StatusOneOf, ", ")), + Why: errors.Meta{ + "Status": value, + }, + }) + } + + return valueObj, nil +} diff --git a/pkg/context/shared/domain/messages/valueobjs/status.mother.go b/pkg/context/shared/domain/messages/valueobjs/status.mother.go new file mode 100644 index 0000000..42f684a --- /dev/null +++ b/pkg/context/shared/domain/messages/valueobjs/status.mother.go @@ -0,0 +1,24 @@ +package valueobjs + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/services" +) + +func StatusWithValidValue() *Status { + value, err := NewStatus(services.Create.RandomString([]string{"queued", "succeeded", "failed", "done"})) + + if err != nil { + errors.Panic(err.Error(), "StatusWithValidValue") + } + + return value +} + +func StatusWithInvalidValue() (string, error) { + value := "x" + + _, err := NewStatus(value) + + return value, err +} diff --git a/pkg/context/shared/domain/messages/valueobjs/status_test.go b/pkg/context/shared/domain/messages/valueobjs/status_test.go new file mode 100644 index 0000000..b50b492 --- /dev/null +++ b/pkg/context/shared/domain/messages/valueobjs/status_test.go @@ -0,0 +1,38 @@ +package valueobjs_test + +import ( + "testing" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages/valueobjs" + "github.com/stretchr/testify/suite" +) + +type StatusValueObjectTestSuite struct { + suite.Suite +} + +func (suite *StatusValueObjectTestSuite) SetupTest() {} + +func (suite *StatusValueObjectTestSuite) TestWithInvalidValue() { + value, err := valueobjs.StatusWithInvalidValue() + + var actual *errors.ErrInvalidValue + + suite.ErrorAs(err, &actual) + + expected := &errors.ErrInvalidValue{Bubble: &errors.Bubble{ + When: actual.When, + Where: "NewStatus", + What: "Status must be only one of these values: Queued, Succeeded, Failed, Done", + Why: errors.Meta{ + "Status": value, + }, + }} + + suite.EqualError(expected, actual.Error()) +} + +func TestUnitStatusValueObjectSuite(t *testing.T) { + suite.Run(t, new(StatusValueObjectTestSuite)) +} diff --git a/pkg/context/shared/domain/messages/valueobjs/type.go b/pkg/context/shared/domain/messages/valueobjs/type.go new file mode 100644 index 0000000..2ab0bc6 --- /dev/null +++ b/pkg/context/shared/domain/messages/valueobjs/type.go @@ -0,0 +1,37 @@ +package valueobjs + +import ( + "fmt" + "strings" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/services" +) + +var TypeOneOf = []string{"Event", "Command"} + +type Type struct { + Value string `validate:"oneof=event command"` +} + +func NewType(value string) (*Type, error) { + value = strings.TrimSpace(value) + + value = strings.ToLower(value) + + valueObj := &Type{ + Value: value, + } + + if services.IsValueObjectInvalid(valueObj) { + return nil, errors.NewInvalidValue(&errors.Bubble{ + Where: "NewType", + What: fmt.Sprintf("Type must be only one of these values: %s", strings.Join(TypeOneOf, ", ")), + Why: errors.Meta{ + "Type": value, + }, + }) + } + + return valueObj, nil +} diff --git a/pkg/context/shared/domain/messages/valueobjs/type.mother.go b/pkg/context/shared/domain/messages/valueobjs/type.mother.go new file mode 100644 index 0000000..6f9f453 --- /dev/null +++ b/pkg/context/shared/domain/messages/valueobjs/type.mother.go @@ -0,0 +1,24 @@ +package valueobjs + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/services" +) + +func TypeWithValidValue() *Type { + value, err := NewType(services.Create.RandomString([]string{"event", "command"})) + + if err != nil { + errors.Panic(err.Error(), "TypeWithValidValue") + } + + return value +} + +func TypeWithInvalidValue() (string, error) { + value := "x" + + _, err := NewType(value) + + return value, err +} diff --git a/pkg/context/shared/domain/messages/valueobjs/type_test.go b/pkg/context/shared/domain/messages/valueobjs/type_test.go new file mode 100644 index 0000000..b16d9a8 --- /dev/null +++ b/pkg/context/shared/domain/messages/valueobjs/type_test.go @@ -0,0 +1,38 @@ +package valueobjs_test + +import ( + "testing" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages/valueobjs" + "github.com/stretchr/testify/suite" +) + +type TypeValueObjectTestSuite struct { + suite.Suite +} + +func (suite *TypeValueObjectTestSuite) SetupTest() {} + +func (suite *TypeValueObjectTestSuite) TestWithInvalidValue() { + value, err := valueobjs.TypeWithInvalidValue() + + var actual *errors.ErrInvalidValue + + suite.ErrorAs(err, &actual) + + expected := &errors.ErrInvalidValue{Bubble: &errors.Bubble{ + When: actual.When, + Where: "NewType", + What: "Type must be only one of these values: Event, Command", + Why: errors.Meta{ + "Type": value, + }, + }} + + suite.EqualError(expected, actual.Error()) +} + +func TestUnitTypeValueObjectSuite(t *testing.T) { + suite.Run(t, new(TypeValueObjectTestSuite)) +} diff --git a/pkg/context/shared/domain/messages/valueobjs/version.go b/pkg/context/shared/domain/messages/valueobjs/version.go new file mode 100644 index 0000000..1c5a087 --- /dev/null +++ b/pkg/context/shared/domain/messages/valueobjs/version.go @@ -0,0 +1,32 @@ +package valueobjs + +import ( + "strings" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/services" +) + +type Version struct { + Value string `validate:"number"` +} + +func NewVersion(value string) (*Version, error) { + value = strings.TrimSpace(value) + + valueObj := &Version{ + Value: value, + } + + if services.IsValueObjectInvalid(valueObj) { + return nil, errors.NewInvalidValue(&errors.Bubble{ + Where: "NewVersion", + What: "Version must be numeric only", + Why: errors.Meta{ + "Version": value, + }, + }) + } + + return valueObj, nil +} diff --git a/pkg/context/shared/domain/messages/valueobjs/version.mother.go b/pkg/context/shared/domain/messages/valueobjs/version.mother.go new file mode 100644 index 0000000..9488d47 --- /dev/null +++ b/pkg/context/shared/domain/messages/valueobjs/version.mother.go @@ -0,0 +1,24 @@ +package valueobjs + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/services" +) + +func VersionWithValidValue() *Version { + value, err := NewVersion(services.Create.Regex(`^[0-9]{1,2}$`)) + + if err != nil { + errors.Panic(err.Error(), "VersionWithValidValue") + } + + return value +} + +func VersionWithInvalidValue() (string, error) { + value := "x" + + _, err := NewVersion(value) + + return value, err +} diff --git a/pkg/context/shared/domain/messages/valueobjs/version_test.go b/pkg/context/shared/domain/messages/valueobjs/version_test.go new file mode 100644 index 0000000..52bb978 --- /dev/null +++ b/pkg/context/shared/domain/messages/valueobjs/version_test.go @@ -0,0 +1,38 @@ +package valueobjs_test + +import ( + "testing" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages/valueobjs" + "github.com/stretchr/testify/suite" +) + +type VersionValueObjectTestSuite struct { + suite.Suite +} + +func (suite *VersionValueObjectTestSuite) SetupTest() {} + +func (suite *VersionValueObjectTestSuite) TestWithInvalidValue() { + value, err := valueobjs.VersionWithInvalidValue() + + var actual *errors.ErrInvalidValue + + suite.ErrorAs(err, &actual) + + expected := &errors.ErrInvalidValue{Bubble: &errors.Bubble{ + When: actual.When, + Where: "NewVersion", + What: "Version must be numeric only", + Why: errors.Meta{ + "Version": value, + }, + }} + + suite.EqualError(expected, actual.Error()) +} + +func TestUnitVersionValueObjectSuite(t *testing.T) { + suite.Run(t, new(VersionValueObjectTestSuite)) +} diff --git a/pkg/context/shared/domain/services/context.go b/pkg/context/shared/domain/services/context.go new file mode 100644 index 0000000..61e921c --- /dev/null +++ b/pkg/context/shared/domain/services/context.go @@ -0,0 +1,17 @@ +package services + +import ( + "context" +) + +type CtxKey string + +const CtxDefaultKey CtxKey = "default" + +func SetDefaultContextValue(value any) context.Context { + return context.WithValue(context.Background(), CtxDefaultKey, value) +} + +func GetDefaultContextValue[T any](ctx context.Context) T { + return ctx.Value(CtxDefaultKey).(T) +} diff --git a/pkg/context/shared/domain/services/mother.go b/pkg/context/shared/domain/services/mother.go new file mode 100644 index 0000000..a426aee --- /dev/null +++ b/pkg/context/shared/domain/services/mother.go @@ -0,0 +1,23 @@ +package services + +import ( + "fmt" + "strings" + + "github.com/brianvoe/gofakeit/v7" +) + +type mother struct { + *gofakeit.Faker +} + +func (create *mother) Email() string { + username := strings.Split(create.Faker.Email(), "@")[0] + domain := "example.com" + + return fmt.Sprintf("%s@%s", username, domain) +} + +var Create = &mother{ + Faker: gofakeit.New(0), +} diff --git a/pkg/context/shared/domain/services/validate.go b/pkg/context/shared/domain/services/validate.go new file mode 100644 index 0000000..536b3f9 --- /dev/null +++ b/pkg/context/shared/domain/services/validate.go @@ -0,0 +1,11 @@ +package services + +import ( + "github.com/go-playground/validator/v10" +) + +var validate = validator.New(validator.WithRequiredStructEnabled()).Struct + +func IsValueObjectInvalid(valueObj any) bool { + return validate(valueObj) != nil +} diff --git a/pkg/context/shared/domain/transfers/transfer.go b/pkg/context/shared/domain/transfers/transfer.go new file mode 100644 index 0000000..5679657 --- /dev/null +++ b/pkg/context/shared/domain/transfers/transfer.go @@ -0,0 +1,5 @@ +package transfers + +type Transfer interface { + Submit(any) error +} diff --git a/pkg/context/shared/infrastructure/authentications/jwt/jwt.go b/pkg/context/shared/infrastructure/authentications/jwt/jwt.go new file mode 100644 index 0000000..b6d5211 --- /dev/null +++ b/pkg/context/shared/infrastructure/authentications/jwt/jwt.go @@ -0,0 +1,52 @@ +package jwt + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/golang-jwt/jwt/v5" +) + +type Payload = map[string]any + +type JWT struct { + secretKey []byte +} + +func (auth *JWT) Generate(payload map[string]any) (string, error) { + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims(payload)) + + signature, err := token.SignedString(auth.secretKey) + + if err != nil { + return "", errors.NewInternal(&errors.Bubble{ + Where: "Generate", + What: "Failure to sign a JWT", + Who: err, + }) + } + + return signature, nil +} + +func (auth *JWT) Validate(signature string) (jwt.MapClaims, error) { + token, _ := jwt.Parse(signature, func(token *jwt.Token) (any, error) { + return auth.secretKey, nil + }) + + if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { + return claims, nil + } + + return nil, errors.NewFailure(&errors.Bubble{ + Where: "Validate", + What: "Invalid JWT signature", + Why: errors.Meta{ + "Signature": signature, + }, + }) +} + +func New(secretKey string) *JWT { + return &JWT{ + secretKey: []byte(secretKey), + } +} diff --git a/pkg/context/shared/infrastructure/communications/broker.mock.go b/pkg/context/shared/infrastructure/communications/broker.mock.go new file mode 100644 index 0000000..fa49371 --- /dev/null +++ b/pkg/context/shared/infrastructure/communications/broker.mock.go @@ -0,0 +1,35 @@ +package communications + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages" + "github.com/stretchr/testify/mock" +) + +type BrokerMock struct { + mock.Mock +} + +func (broker *BrokerMock) PublishMessages(messages []*messages.Message) error { + broker.Called(messages) + return nil +} + +func (broker *BrokerMock) AddRouter(router *messages.Router) error { + broker.Called(router) + return nil +} + +func (broker *BrokerMock) AddQueue(queue *messages.Queue) error { + broker.Called(queue) + return nil +} + +func (broker *BrokerMock) AddQueueMessageBind(queue *messages.Queue, bindingKeys []string) error { + broker.Called(queue, bindingKeys) + return nil +} + +func (broker *BrokerMock) AddQueueConsumer(consumer messages.Consumer) error { + broker.Called(consumer) + return nil +} diff --git a/pkg/context/shared/infrastructure/communications/consumer.mock.go b/pkg/context/shared/infrastructure/communications/consumer.mock.go new file mode 100644 index 0000000..d9a24a1 --- /dev/null +++ b/pkg/context/shared/infrastructure/communications/consumer.mock.go @@ -0,0 +1,20 @@ +package communications + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages" + "github.com/stretchr/testify/mock" +) + +type ConsumerMock struct { + mock.Mock +} + +func (consumer *ConsumerMock) SubscribedTo() []*messages.Queue { + args := consumer.Called() + return args.Get(0).([]*messages.Queue) +} + +func (consumer *ConsumerMock) On(message *messages.Message) error { + // TODO?(goroutine): consumer.Called(message) + return nil +} diff --git a/pkg/context/shared/infrastructure/communications/rabbitmq/rabbitmq.go b/pkg/context/shared/infrastructure/communications/rabbitmq/rabbitmq.go new file mode 100644 index 0000000..788298a --- /dev/null +++ b/pkg/context/shared/infrastructure/communications/rabbitmq/rabbitmq.go @@ -0,0 +1,290 @@ +package rabbitmq + +import ( + "context" + "encoding/json" + "fmt" + "time" + + "github.com/google/uuid" + amqp "github.com/rabbitmq/amqp091-go" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/loggers" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages" +) + +type RabbitMQ struct { + *amqp.Connection + *amqp.Channel + loggers.Logger + exchange string +} + +func (rabbitMQ *RabbitMQ) AddRouter(router *messages.Router) error { + err := rabbitMQ.Channel.ExchangeDeclare( + router.Name, + "topic", + true, + false, + false, + false, + nil, + ) + + if err != nil { + return errors.NewInternal(&errors.Bubble{ + Where: "AddRouter", + What: "Failure to declare a router", + Why: errors.Meta{ + "Router": router.Name, + }, + Who: err, + }) + } + + rabbitMQ.exchange = router.Name + + return nil +} + +func (rabbitMQ *RabbitMQ) AddQueue(queue *messages.Queue) error { + _, err := rabbitMQ.Channel.QueueDeclare( + queue.Name, + true, + false, + false, + false, + nil, + ) + + if err != nil { + return errors.NewInternal(&errors.Bubble{ + Where: "AddQueue", + What: "Failure to declare a queue", + Why: errors.Meta{ + "Queue": queue.Name, + }, + Who: err, + }) + } + + return nil +} + +func (rabbitMQ *RabbitMQ) AddQueueMessageBind(queue *messages.Queue, bindingKeys []string) error { + var errWrap error + + for _, bindingKey := range bindingKeys { + rabbitMQ.Logger.Info(fmt.Sprintf("binding queue [%s] to exchange [%s] with binding key [%s]", queue.Name, rabbitMQ.exchange, bindingKey)) + + err := rabbitMQ.Channel.QueueBind( + queue.Name, + bindingKey, + rabbitMQ.exchange, + false, + nil, + ) + + if err != nil { + errToWrap := errors.NewInternal(&errors.Bubble{ + Where: "AddQueueMessageBind", + What: "Failure to bind a queue", + Why: errors.Meta{ + "Queue": queue.Name, + "Binding Key": bindingKey, + "Exchange": rabbitMQ.exchange, + }, + Who: err, + }) + + errWrap = errors.Join(errWrap, errToWrap) + } + } + + if errWrap != nil { + return errors.BubbleUp(errWrap, "AddQueueMessageBind") + } + + return nil +} + +func (rabbitMQ *RabbitMQ) AddQueueConsumer(consumer messages.Consumer) error { + var errWrap error + + for _, queue := range consumer.SubscribedTo() { + deliveries, err := rabbitMQ.Channel.Consume( + queue.Name, + "", + false, + false, + false, + false, + nil, + ) + + if err != nil { + errToWrap := errors.NewInternal(&errors.Bubble{ + Where: "AddQueueConsumer", + What: "Failure to register a consumer", + Why: errors.Meta{ + "Queue": queue.Name, + "Exchange": rabbitMQ.exchange, + }, + Who: err, + }) + + errWrap = errors.Join(errWrap, errToWrap) + + continue + } + + go func() { + for delivery := range deliveries { + message := new(messages.Message) + + err := json.Unmarshal(delivery.Body, message) + + if err != nil { + rabbitMQ.Logger.Error(fmt.Sprintf("failed to deliver a message with id [%s] from queue [%s]", message.Id, queue.Name)) + continue + } + + err = consumer.On(message) + + if err != nil { + rabbitMQ.Logger.Error(fmt.Sprintf("failed to consume a message with id [%s] from queue [%s]", message.Id, queue.Name)) + continue + } + + delivery.Ack(false) + } + }() + } + + if errWrap != nil { + return errors.BubbleUp(errWrap, "AddQueueConsumer") + } + + return nil +} + +func (rabbitMQ *RabbitMQ) PublishMessages(messages []*messages.Message) error { + var errWrap error + + for _, message := range messages { + if message.Id == "" { + message.Id = uuid.NewString() + } + + if message.OccurredOn == "" { + message.OccurredOn = time.Now().UTC().Format(time.RFC3339Nano) + } + + body, err := json.Marshal(message) + + if err != nil { + errToWrap := errors.NewInternal(&errors.Bubble{ + Where: "PublishMessages", + What: "Cannot encode message to JSON", + Why: errors.Meta{ + "Exchange": rabbitMQ.exchange, + "Message": message.Id, + }, + Who: err, + }) + + errWrap = errors.Join(errWrap, errToWrap) + + continue + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + + defer cancel() + + err = rabbitMQ.Channel.PublishWithContext(ctx, + rabbitMQ.exchange, + message.Type, + false, + false, + amqp.Publishing{ + DeliveryMode: amqp.Persistent, + ContentType: "application/json", + Body: body, + }, + ) + + if err != nil { + errToWrap := errors.NewInternal(&errors.Bubble{ + Where: "PublishMessages", + What: "Failure to publish a message", + Why: errors.Meta{ + "Exchange": rabbitMQ.exchange, + "Message": message.Id, + }, + Who: err, + }) + + errWrap = errors.Join(errWrap, errToWrap) + } + } + + if errWrap != nil { + return errors.BubbleUp(errWrap, "PublishMessages") + } + + return nil +} + +func Open(uri string, logger loggers.Logger) (*RabbitMQ, error) { + session, err := amqp.Dial(uri) + + if err != nil { + return nil, errors.NewInternal(&errors.Bubble{ + Where: "Open", + What: "Failure connecting to RabbitMQ", + Who: err, + }) + } + + channel, err := session.Channel() + + if err != nil { + return nil, errors.NewInternal(&errors.Bubble{ + Where: "Open", + What: "Failure to open a channel", + Who: err, + }) + } + + return &RabbitMQ{ + Connection: session, + Channel: channel, + Logger: logger, + }, nil +} + +func Close(session *RabbitMQ) error { + err := session.Channel.Close() + + if err != nil { + return errors.NewInternal(&errors.Bubble{ + Where: "Close", + What: "Failure to close channel", + Who: err, + }) + } + + err = session.Connection.Close() + + if err != nil { + return errors.NewInternal(&errors.Bubble{ + Where: "Close", + What: "Failure to close RabbitMQ connection", + Who: err, + }) + } + + return nil +} diff --git a/pkg/context/shared/infrastructure/communications/rabbitmq/rabbitmq_test.go b/pkg/context/shared/infrastructure/communications/rabbitmq/rabbitmq_test.go new file mode 100644 index 0000000..5674bed --- /dev/null +++ b/pkg/context/shared/infrastructure/communications/rabbitmq/rabbitmq_test.go @@ -0,0 +1,97 @@ +package rabbitmq_test + +import ( + "fmt" + "os" + "testing" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages" + "github.com/bastean/codexgo/v4/pkg/context/shared/infrastructure/communications" + "github.com/bastean/codexgo/v4/pkg/context/shared/infrastructure/communications/rabbitmq" + "github.com/bastean/codexgo/v4/pkg/context/shared/infrastructure/records" + "github.com/stretchr/testify/suite" +) + +type RabbitMQTestSuite struct { + suite.Suite + sut messages.Broker + logger *records.LoggerMock + router *messages.Router + queue *messages.Queue + consumer *communications.ConsumerMock + messages []*messages.Message +} + +func (suite *RabbitMQTestSuite) SetupTest() { + suite.logger = new(records.LoggerMock) + + suite.sut, _ = rabbitmq.Open( + os.Getenv("BROKER_RABBITMQ_URI"), + suite.logger, + ) + + suite.router = &messages.Router{ + Name: os.Getenv("BROKER_RABBITMQ_NAME"), + } + + suite.queue = &messages.Queue{ + Name: messages.NewRecipientName(&messages.RecipientNameComponents{ + Service: "queue", + Entity: "queue", + Action: "assert", + Event: "test", + Status: "succeeded", + }), + } + + suite.consumer = new(communications.ConsumerMock) + + message := messages.New( + messages.NewRoutingKey(&messages.RoutingKeyComponents{ + Service: "publisher", + Version: "1", + Type: messages.Type.Event, + Entity: "publisher", + Event: "test", + Status: messages.Status.Succeeded, + }), + messages.Attributes{}, + messages.Meta{}, + ) + + message.Id = "0" + + message.OccurredOn = "0" + + suite.messages = append(suite.messages, message) +} + +func (suite *RabbitMQTestSuite) TestBroker() { + suite.NoError(suite.sut.AddRouter(suite.router)) + + suite.NoError(suite.sut.AddQueue(suite.queue)) + + bindingKeys := []string{"#.event.#.test.succeeded"} + + bindingSucceeded := fmt.Sprintf("binding queue [%s] to exchange [%s] with binding key [%s]", suite.queue.Name, suite.router.Name, bindingKeys[0]) + + suite.logger.Mock.On("Info", bindingSucceeded) + + suite.NoError(suite.sut.AddQueueMessageBind(suite.queue, bindingKeys)) + + suite.consumer.Mock.On("SubscribedTo").Return([]*messages.Queue{suite.queue}) + + suite.NoError(suite.sut.AddQueueConsumer(suite.consumer)) + + // TODO?(goroutine): suite.consumer.Mock.On("On", suite.messages[0]) + + suite.NoError(suite.sut.PublishMessages(suite.messages)) + + suite.logger.AssertExpectations(suite.T()) + + suite.consumer.AssertExpectations(suite.T()) +} + +func TestIntegrationRabbitMQSuite(t *testing.T) { + suite.Run(t, new(RabbitMQTestSuite)) +} diff --git a/pkg/context/shared/infrastructure/persistences/mongodb/mongodb.go b/pkg/context/shared/infrastructure/persistences/mongodb/mongodb.go new file mode 100644 index 0000000..ed7a9e3 --- /dev/null +++ b/pkg/context/shared/infrastructure/persistences/mongodb/mongodb.go @@ -0,0 +1,90 @@ +package mongodb + +import ( + "context" + "fmt" + "regexp" + "strings" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +type MongoDB struct { + *mongo.Client + *mongo.Database +} + +func Open(uri, name string) (*MongoDB, error) { + options := options.Client().ApplyURI(uri) + + session, err := mongo.Connect(context.Background(), options) + + if err != nil { + return nil, errors.NewInternal(&errors.Bubble{ + Where: "Open", + What: "Failure to create a MongoDB client", + Who: err, + }) + } + + err = session.Ping(context.Background(), nil) + + if err != nil { + return nil, errors.NewInternal(&errors.Bubble{ + Where: "Open", + What: "Failure connecting to MongoDB", + Who: err, + }) + } + + return &MongoDB{ + Client: session, + Database: session.Database(name), + }, nil +} + +func Close(ctx context.Context, session *MongoDB) error { + if err := session.Client.Disconnect(ctx); err != nil { + return errors.NewInternal(&errors.Bubble{ + Where: "Close", + What: "Failure to close connection with MongoDB", + Who: err, + }) + } + + return nil +} + +func HandleDuplicateKeyError(err error) error { + re := regexp.MustCompile(`{ [A-Za-z0-9]+:`) + + rawField := re.FindString(err.Error()) + + toTitle := cases.Title(language.English) + + field := toTitle.String(strings.TrimSuffix(strings.Split(rawField, " ")[1], ":")) + + return errors.NewAlreadyExist(&errors.Bubble{ + Where: "HandleDuplicateKeyError", + What: fmt.Sprintf("%s already registered", field), + Why: errors.Meta{ + "Field": field, + }, + Who: err, + }) +} + +func HandleDocumentNotFound(index string, err error) error { + return errors.NewNotExist(&errors.Bubble{ + Where: "HandleDocumentNotFound", + What: fmt.Sprintf("%s not found", index), + Why: errors.Meta{ + "Index": index, + }, + Who: err, + }) +} diff --git a/pkg/context/shared/infrastructure/records/log/log.go b/pkg/context/shared/infrastructure/records/log/log.go new file mode 100644 index 0000000..30ec9ef --- /dev/null +++ b/pkg/context/shared/infrastructure/records/log/log.go @@ -0,0 +1,40 @@ +package log + +import ( + "log" + + "github.com/fatih/color" +) + +type Log struct { + Cyan, Red, Blue, Green *color.Color +} + +func (color *Log) Debug(message string) { + log.Println(color.Cyan.Sprint(message)) +} + +func (color *Log) Error(message string) { + log.Println(color.Red.Sprint(message)) +} + +func (color *Log) Fatal(message string) { + log.Fatal(color.Red.Sprint(message)) +} + +func (color *Log) Info(message string) { + log.Println(color.Blue.Sprint(message)) +} + +func (color *Log) Success(message string) { + log.Println(color.Green.Sprint(message)) +} + +func New() *Log { + return &Log{ + Cyan: color.New(color.FgCyan, color.Bold), + Red: color.New(color.FgRed, color.Bold), + Blue: color.New(color.FgBlue, color.Bold), + Green: color.New(color.FgGreen, color.Bold), + } +} diff --git a/pkg/context/shared/infrastructure/records/logger.mock.go b/pkg/context/shared/infrastructure/records/logger.mock.go new file mode 100644 index 0000000..1bd2cab --- /dev/null +++ b/pkg/context/shared/infrastructure/records/logger.mock.go @@ -0,0 +1,29 @@ +package records + +import ( + "github.com/stretchr/testify/mock" +) + +type LoggerMock struct { + mock.Mock +} + +func (logger *LoggerMock) Debug(message string) { + logger.Called(message) +} + +func (logger *LoggerMock) Error(message string) { + logger.Called(message) +} + +func (logger *LoggerMock) Fatal(message string) { + logger.Called(message) +} + +func (logger *LoggerMock) Info(message string) { + logger.Called(message) +} + +func (logger *LoggerMock) Success(message string) { + logger.Called(message) +} diff --git a/pkg/context/shared/infrastructure/transports/smtp/smtp.go b/pkg/context/shared/infrastructure/transports/smtp/smtp.go new file mode 100644 index 0000000..bb205e3 --- /dev/null +++ b/pkg/context/shared/infrastructure/transports/smtp/smtp.go @@ -0,0 +1,45 @@ +package smtp + +import ( + "fmt" + "net/smtp" +) + +type SMTP struct { + smtp.Auth + SMTPServerURL, Username, Password, ServerURL string +} + +func (*SMTP) SetHeader(key, value string) string { + return fmt.Sprintf("%s: %s\n", key, value) +} + +func (client *SMTP) Headers(to, subject string) string { + header := client.SetHeader("From", client.Username) + + header += client.SetHeader("To", to) + + header += client.SetHeader("Subject", subject) + + header += client.SetHeader("MIME-version", "1.0") + + header += client.SetHeader("Content-Type", "text/html; charset=\"UTF-8\"") + + header += "\n" + + return header +} + +func (client *SMTP) SendMail(to []string, message []byte) error { + return smtp.SendMail(client.SMTPServerURL, client.Auth, client.Username, to, message) +} + +func Open(host, port, username, password, serverURL string) *SMTP { + return &SMTP{ + Auth: smtp.PlainAuth("", username, password, host), + SMTPServerURL: fmt.Sprintf("%s:%s", host, port), + Username: username, + Password: password, + ServerURL: serverURL, + } +} diff --git a/pkg/context/shared/infrastructure/transports/transfer.mock.go b/pkg/context/shared/infrastructure/transports/transfer.mock.go new file mode 100644 index 0000000..4c3af12 --- /dev/null +++ b/pkg/context/shared/infrastructure/transports/transfer.mock.go @@ -0,0 +1,14 @@ +package transports + +import ( + "github.com/stretchr/testify/mock" +) + +type TransferMock struct { + mock.Mock +} + +func (transfer *TransferMock) Submit(data any) error { + transfer.Called(data) + return nil +} diff --git a/pkg/context/user/application/create/command.go b/pkg/context/user/application/create/command.go new file mode 100644 index 0000000..1431c5a --- /dev/null +++ b/pkg/context/user/application/create/command.go @@ -0,0 +1,5 @@ +package create + +type Command struct { + Id, Email, Username, Password string +} diff --git a/pkg/context/user/application/create/command.handler.go b/pkg/context/user/application/create/command.handler.go new file mode 100644 index 0000000..4f48c7f --- /dev/null +++ b/pkg/context/user/application/create/command.handler.go @@ -0,0 +1,36 @@ +package create + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/usecase" +) + +type Handler struct { + usecase.Create + messages.Broker +} + +func (handler *Handler) Handle(command *Command) error { + new, err := user.New(&user.Primitive{ + Id: command.Id, + Email: command.Email, + Username: command.Username, + Password: command.Password, + }) + + if err != nil { + return errors.BubbleUp(err, "Handle") + } + + err = handler.Create.Run(new) + + if err != nil { + return errors.BubbleUp(err, "Handle") + } + + handler.Broker.PublishMessages(new.PullMessages()) + + return nil +} diff --git a/pkg/context/user/application/create/command.handler_test.go b/pkg/context/user/application/create/command.handler_test.go new file mode 100644 index 0000000..3625d52 --- /dev/null +++ b/pkg/context/user/application/create/command.handler_test.go @@ -0,0 +1,63 @@ +package create_test + +import ( + "testing" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/handlers" + "github.com/bastean/codexgo/v4/pkg/context/shared/infrastructure/communications" + "github.com/bastean/codexgo/v4/pkg/context/user/application/create" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/usecase" + "github.com/bastean/codexgo/v4/pkg/context/user/infrastructure/persistence" + "github.com/stretchr/testify/suite" +) + +type CreateTestSuite struct { + suite.Suite + sut handlers.Command[*create.Command] + create usecase.Create + repository *persistence.UserMock + broker *communications.BrokerMock +} + +func (suite *CreateTestSuite) SetupTest() { + suite.broker = new(communications.BrokerMock) + + suite.repository = new(persistence.UserMock) + + suite.create = &create.Create{ + User: suite.repository, + } + + suite.sut = &create.Handler{ + Create: suite.create, + Broker: suite.broker, + } +} + +func (suite *CreateTestSuite) TestCreate() { + command := create.RandomCommand() + + new, _ := user.New(&user.Primitive{ + Id: command.Id, + Email: command.Email, + Username: command.Username, + Password: command.Password, + }) + + messages := new.Messages + + suite.repository.On("Save", new) + + suite.broker.On("PublishMessages", messages) + + suite.NoError(suite.sut.Handle(command)) + + suite.repository.AssertExpectations(suite.T()) + + suite.broker.AssertExpectations(suite.T()) +} + +func TestUnitCreateSuite(t *testing.T) { + suite.Run(t, new(CreateTestSuite)) +} diff --git a/pkg/context/user/application/create/command.mother.go b/pkg/context/user/application/create/command.mother.go new file mode 100644 index 0000000..d5d1d4f --- /dev/null +++ b/pkg/context/user/application/create/command.mother.go @@ -0,0 +1,19 @@ +package create + +import ( + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" +) + +func RandomCommand() *Command { + id := user.IdWithValidValue() + email := user.EmailWithValidValue() + username := user.UsernameWithValidValue() + password := user.PasswordWithValidValue() + + return &Command{ + Id: id.Value, + Email: email.Value, + Username: username.Value, + Password: password.Value, + } +} diff --git a/pkg/context/user/application/create/create.go b/pkg/context/user/application/create/create.go new file mode 100644 index 0000000..dcf7752 --- /dev/null +++ b/pkg/context/user/application/create/create.go @@ -0,0 +1,21 @@ +package create + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/repository" +) + +type Create struct { + repository.User +} + +func (create *Create) Run(user *user.User) error { + err := create.User.Save(user) + + if err != nil { + return errors.BubbleUp(err, "Run") + } + + return nil +} diff --git a/pkg/context/user/application/created/created.consumer.go b/pkg/context/user/application/created/created.consumer.go new file mode 100644 index 0000000..5bbbae3 --- /dev/null +++ b/pkg/context/user/application/created/created.consumer.go @@ -0,0 +1,48 @@ +package created + +import ( + "encoding/json" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/usecase" +) + +type Consumer struct { + usecase.Created + Queues []*messages.Queue +} + +func (consumer *Consumer) SubscribedTo() []*messages.Queue { + return consumer.Queues +} + +func (consumer *Consumer) On(message *messages.Message) error { + event := new(user.CreatedSucceeded) + + event.Attributes = new(user.CreatedSucceededAttributes) + + err := json.Unmarshal(message.Attributes, event.Attributes) + + if err != nil { + return errors.NewInternal(&errors.Bubble{ + Where: "On", + What: "Failure to obtain message attributes", + Why: errors.Meta{ + "Id": message.Id, + "Routing Key": message.Type, + "Occurred On": message.OccurredOn, + }, + Who: err, + }) + } + + err = consumer.Created.Run(event) + + if err != nil { + return errors.BubbleUp(err, "On") + } + + return nil +} diff --git a/pkg/context/user/application/created/created.consumer_test.go b/pkg/context/user/application/created/created.consumer_test.go new file mode 100644 index 0000000..e845867 --- /dev/null +++ b/pkg/context/user/application/created/created.consumer_test.go @@ -0,0 +1,66 @@ +package created_test + +import ( + "encoding/json" + "testing" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages" + "github.com/bastean/codexgo/v4/pkg/context/shared/infrastructure/transports" + "github.com/bastean/codexgo/v4/pkg/context/user/application/created" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/usecase" + "github.com/stretchr/testify/suite" +) + +type CreatedConsumerTestSuite struct { + suite.Suite + sut messages.Consumer + created usecase.Created + transfer *transports.TransferMock + queues []*messages.Queue +} + +func (suite *CreatedConsumerTestSuite) SetupTest() { + queueName := messages.NewRecipientName(&messages.RecipientNameComponents{ + Service: "queue", + Entity: "queue", + Action: "assert", + Event: "test", + Status: "succeeded", + }) + + suite.queues = append(suite.queues, &messages.Queue{ + Name: queueName, + }) + + suite.transfer = new(transports.TransferMock) + + suite.created = &created.Created{ + Transfer: suite.transfer, + } + + suite.sut = &created.Consumer{ + Created: suite.created, + Queues: suite.queues, + } +} + +func (suite *CreatedConsumerTestSuite) TestCreatedSucceeded() { + message := user.RandomCreatedSucceeded() + + event := new(user.CreatedSucceeded) + + event.Attributes = new(user.CreatedSucceededAttributes) + + suite.NoError(json.Unmarshal(message.Attributes, event.Attributes)) + + suite.transfer.On("Submit", event.Attributes) + + suite.NoError(suite.sut.On(message)) + + suite.transfer.AssertExpectations(suite.T()) +} + +func TestUnitCreatedConsumerSuite(t *testing.T) { + suite.Run(t, new(CreatedConsumerTestSuite)) +} diff --git a/pkg/context/user/application/created/created.go b/pkg/context/user/application/created/created.go new file mode 100644 index 0000000..0f6c6a5 --- /dev/null +++ b/pkg/context/user/application/created/created.go @@ -0,0 +1,21 @@ +package created + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/transfers" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" +) + +type Created struct { + transfers.Transfer +} + +func (created *Created) Run(event *user.CreatedSucceeded) error { + err := created.Transfer.Submit(event.Attributes) + + if err != nil { + return errors.BubbleUp(err, "Run") + } + + return nil +} diff --git a/pkg/context/user/application/delete/command.go b/pkg/context/user/application/delete/command.go new file mode 100644 index 0000000..0cf11b1 --- /dev/null +++ b/pkg/context/user/application/delete/command.go @@ -0,0 +1,5 @@ +package delete + +type Command struct { + Id, Password string +} diff --git a/pkg/context/user/application/delete/command.handler.go b/pkg/context/user/application/delete/command.handler.go new file mode 100644 index 0000000..bbdf0e6 --- /dev/null +++ b/pkg/context/user/application/delete/command.handler.go @@ -0,0 +1,30 @@ +package delete + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/usecase" +) + +type Handler struct { + usecase.Delete +} + +func (handler *Handler) Handle(command *Command) error { + id, errId := user.NewId(command.Id) + password, errPassword := user.NewPassword(command.Password) + + err := errors.Join(errId, errPassword) + + if err != nil { + return errors.BubbleUp(err, "Handle") + } + + err = handler.Delete.Run(id, password) + + if err != nil { + return errors.BubbleUp(err, "Handle") + } + + return nil +} diff --git a/pkg/context/user/application/delete/command.handler_test.go b/pkg/context/user/application/delete/command.handler_test.go new file mode 100644 index 0000000..460616b --- /dev/null +++ b/pkg/context/user/application/delete/command.handler_test.go @@ -0,0 +1,64 @@ +package delete_test + +import ( + "testing" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/handlers" + "github.com/bastean/codexgo/v4/pkg/context/user/application/delete" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/repository" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/usecase" + "github.com/bastean/codexgo/v4/pkg/context/user/infrastructure/cryptographic" + "github.com/bastean/codexgo/v4/pkg/context/user/infrastructure/persistence" + "github.com/stretchr/testify/suite" +) + +type DeleteTestSuite struct { + suite.Suite + sut handlers.Command[*delete.Command] + delete usecase.Delete + hashing *cryptographic.HashingMock + repository *persistence.UserMock +} + +func (suite *DeleteTestSuite) SetupTest() { + suite.repository = new(persistence.UserMock) + + suite.hashing = new(cryptographic.HashingMock) + + suite.delete = &delete.Delete{ + User: suite.repository, + Hashing: suite.hashing, + } + + suite.sut = &delete.Handler{ + Delete: suite.delete, + } +} + +func (suite *DeleteTestSuite) TestDelete() { + random := user.Random() + + command := &delete.Command{ + Id: random.Id.Value, + Password: random.Password.Value, + } + + criteria := &repository.UserSearchCriteria{ + Id: random.Id, + } + + suite.repository.On("Search", criteria).Return(random) + + suite.hashing.On("IsNotEqual", random.Password.Value, random.Password.Value).Return(false) + + suite.repository.On("Delete", random.Id) + + suite.NoError(suite.sut.Handle(command)) + + suite.repository.AssertExpectations(suite.T()) +} + +func TestUnitDeleteSuite(t *testing.T) { + suite.Run(t, new(DeleteTestSuite)) +} diff --git a/pkg/context/user/application/delete/command.mother.go b/pkg/context/user/application/delete/command.mother.go new file mode 100644 index 0000000..68bb742 --- /dev/null +++ b/pkg/context/user/application/delete/command.mother.go @@ -0,0 +1,15 @@ +package delete + +import ( + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" +) + +func RandomCommand() *Command { + id := user.IdWithValidValue() + password := user.PasswordWithValidValue() + + return &Command{ + Id: id.Value, + Password: password.Value, + } +} diff --git a/pkg/context/user/application/delete/delete.go b/pkg/context/user/application/delete/delete.go new file mode 100644 index 0000000..52874d5 --- /dev/null +++ b/pkg/context/user/application/delete/delete.go @@ -0,0 +1,38 @@ +package delete + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/hashing" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/repository" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/service" +) + +type Delete struct { + repository.User + hashing.Hashing +} + +func (delete *Delete) Run(id *user.Id, password *user.Password) error { + found, err := delete.User.Search(&repository.UserSearchCriteria{ + Id: id, + }) + + if err != nil { + return errors.BubbleUp(err, "Run") + } + + err = service.IsPasswordInvalid(delete.Hashing, found.Password.Value, password.Value) + + if err != nil { + return errors.BubbleUp(err, "Run") + } + + err = delete.User.Delete(found.Id) + + if err != nil { + return errors.BubbleUp(err, "Run") + } + + return nil +} diff --git a/pkg/context/user/application/login/login.go b/pkg/context/user/application/login/login.go new file mode 100644 index 0000000..4de5980 --- /dev/null +++ b/pkg/context/user/application/login/login.go @@ -0,0 +1,32 @@ +package login + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/hashing" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/repository" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/service" +) + +type Login struct { + repository.User + hashing.Hashing +} + +func (login *Login) Run(email *user.Email, password *user.Password) (*user.User, error) { + found, err := login.User.Search(&repository.UserSearchCriteria{ + Email: email, + }) + + if err != nil { + return nil, errors.BubbleUp(err, "Run") + } + + err = service.IsPasswordInvalid(login.Hashing, found.Password.Value, password.Value) + + if err != nil { + return nil, errors.BubbleUp(err, "Run") + } + + return found, nil +} diff --git a/pkg/context/user/application/login/query.go b/pkg/context/user/application/login/query.go new file mode 100644 index 0000000..f087799 --- /dev/null +++ b/pkg/context/user/application/login/query.go @@ -0,0 +1,5 @@ +package login + +type Query struct { + Email, Password string +} diff --git a/pkg/context/user/application/login/query.handler.go b/pkg/context/user/application/login/query.handler.go new file mode 100644 index 0000000..cd705c8 --- /dev/null +++ b/pkg/context/user/application/login/query.handler.go @@ -0,0 +1,32 @@ +package login + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/usecase" +) + +type Handler struct { + usecase.Login +} + +func (handler *Handler) Handle(query *Query) (*Response, error) { + email, errEmail := user.NewEmail(query.Email) + password, errPassword := user.NewPassword(query.Password) + + err := errors.Join(errEmail, errPassword) + + if err != nil { + return nil, errors.BubbleUp(err, "Handle") + } + + found, err := handler.Login.Run(email, password) + + if err != nil { + return nil, errors.BubbleUp(err, "Handle") + } + + response := Response(*found.ToPrimitive()) + + return &response, nil +} diff --git a/pkg/context/user/application/login/query.handler_test.go b/pkg/context/user/application/login/query.handler_test.go new file mode 100644 index 0000000..680814c --- /dev/null +++ b/pkg/context/user/application/login/query.handler_test.go @@ -0,0 +1,70 @@ +package login_test + +import ( + "testing" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/handlers" + "github.com/bastean/codexgo/v4/pkg/context/user/application/login" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/repository" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/usecase" + "github.com/bastean/codexgo/v4/pkg/context/user/infrastructure/cryptographic" + "github.com/bastean/codexgo/v4/pkg/context/user/infrastructure/persistence" + "github.com/stretchr/testify/suite" +) + +type LoginTestSuite struct { + suite.Suite + sut handlers.Query[*login.Query, *login.Response] + login usecase.Login + hashing *cryptographic.HashingMock + repository *persistence.UserMock +} + +func (suite *LoginTestSuite) SetupTest() { + suite.repository = new(persistence.UserMock) + + suite.hashing = new(cryptographic.HashingMock) + + suite.login = &login.Login{ + User: suite.repository, + Hashing: suite.hashing, + } + + suite.sut = &login.Handler{ + Login: suite.login, + } +} + +func (suite *LoginTestSuite) TestLogin() { + random := user.Random() + + query := &login.Query{ + Email: random.Email.Value, + Password: random.Password.Value, + } + + criteria := &repository.UserSearchCriteria{ + Email: random.Email, + } + + suite.repository.On("Search", criteria).Return(random) + + suite.hashing.On("IsNotEqual", random.Password.Value, random.Password.Value).Return(false) + + expected := random.ToPrimitive() + + actual, err := suite.sut.Handle(query) + + suite.NoError(err) + + suite.repository.AssertExpectations(suite.T()) + + suite.hashing.AssertExpectations(suite.T()) + + suite.EqualValues(expected, actual) +} + +func TestUnitLoginSuite(t *testing.T) { + suite.Run(t, new(LoginTestSuite)) +} diff --git a/pkg/context/user/application/login/query.mother.go b/pkg/context/user/application/login/query.mother.go new file mode 100644 index 0000000..16a3da9 --- /dev/null +++ b/pkg/context/user/application/login/query.mother.go @@ -0,0 +1,15 @@ +package login + +import ( + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" +) + +func RandomQuery() *Query { + email := user.EmailWithValidValue() + password := user.PasswordWithValidValue() + + return &Query{ + Email: email.Value, + Password: password.Value, + } +} diff --git a/pkg/context/user/application/login/response.go b/pkg/context/user/application/login/response.go new file mode 100644 index 0000000..b7bf61e --- /dev/null +++ b/pkg/context/user/application/login/response.go @@ -0,0 +1,6 @@ +package login + +type Response struct { + Id, Email, Username, Password string + Verified bool +} diff --git a/pkg/context/user/application/read/query.go b/pkg/context/user/application/read/query.go new file mode 100644 index 0000000..0c83d00 --- /dev/null +++ b/pkg/context/user/application/read/query.go @@ -0,0 +1,5 @@ +package read + +type Query struct { + Id string +} diff --git a/pkg/context/user/application/read/query.handler.go b/pkg/context/user/application/read/query.handler.go new file mode 100644 index 0000000..7a2f102 --- /dev/null +++ b/pkg/context/user/application/read/query.handler.go @@ -0,0 +1,29 @@ +package read + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/usecase" +) + +type Handler struct { + usecase.Read +} + +func (handler *Handler) Handle(query *Query) (*Response, error) { + id, err := user.NewId(query.Id) + + if err != nil { + return nil, errors.BubbleUp(err, "Handle") + } + + found, err := handler.Read.Run(id) + + if err != nil { + return nil, errors.BubbleUp(err, "Handle") + } + + response := Response(*found.ToPrimitive()) + + return &response, nil +} diff --git a/pkg/context/user/application/read/query.handler_test.go b/pkg/context/user/application/read/query.handler_test.go new file mode 100644 index 0000000..545414f --- /dev/null +++ b/pkg/context/user/application/read/query.handler_test.go @@ -0,0 +1,60 @@ +package read_test + +import ( + "testing" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/handlers" + "github.com/bastean/codexgo/v4/pkg/context/user/application/read" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/repository" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/usecase" + "github.com/bastean/codexgo/v4/pkg/context/user/infrastructure/persistence" + "github.com/stretchr/testify/suite" +) + +type ReadTestSuite struct { + suite.Suite + sut handlers.Query[*read.Query, *read.Response] + read usecase.Read + repository *persistence.UserMock +} + +func (suite *ReadTestSuite) SetupTest() { + suite.repository = new(persistence.UserMock) + + suite.read = &read.Read{ + User: suite.repository, + } + + suite.sut = &read.Handler{ + Read: suite.read, + } +} + +func (suite *ReadTestSuite) TestRead() { + random := user.Random() + + query := &read.Query{ + Id: random.Id.Value, + } + + criteria := &repository.UserSearchCriteria{ + Id: random.Id, + } + + suite.repository.On("Search", criteria).Return(random) + + expected := random.ToPrimitive() + + actual, err := suite.sut.Handle(query) + + suite.NoError(err) + + suite.repository.AssertExpectations(suite.T()) + + suite.EqualValues(expected, actual) +} + +func TestUnitReadSuite(t *testing.T) { + suite.Run(t, new(ReadTestSuite)) +} diff --git a/pkg/context/user/application/read/query.mother.go b/pkg/context/user/application/read/query.mother.go new file mode 100644 index 0000000..77163c0 --- /dev/null +++ b/pkg/context/user/application/read/query.mother.go @@ -0,0 +1,13 @@ +package read + +import ( + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" +) + +func RandomQuery() *Query { + id := user.IdWithValidValue() + + return &Query{ + Id: id.Value, + } +} diff --git a/pkg/context/user/application/read/read.go b/pkg/context/user/application/read/read.go new file mode 100644 index 0000000..0350605 --- /dev/null +++ b/pkg/context/user/application/read/read.go @@ -0,0 +1,23 @@ +package read + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/repository" +) + +type Read struct { + repository.User +} + +func (read *Read) Run(id *user.Id) (*user.User, error) { + found, err := read.User.Search(&repository.UserSearchCriteria{ + Id: id, + }) + + if err != nil { + return nil, errors.BubbleUp(err, "Run") + } + + return found, nil +} diff --git a/pkg/context/user/application/read/response.go b/pkg/context/user/application/read/response.go new file mode 100644 index 0000000..c1eb152 --- /dev/null +++ b/pkg/context/user/application/read/response.go @@ -0,0 +1,6 @@ +package read + +type Response struct { + Id, Email, Username, Password string + Verified bool +} diff --git a/pkg/context/user/application/update/command.go b/pkg/context/user/application/update/command.go new file mode 100644 index 0000000..8daf24f --- /dev/null +++ b/pkg/context/user/application/update/command.go @@ -0,0 +1,5 @@ +package update + +type Command struct { + Id, Email, Username, Password, UpdatedPassword string +} diff --git a/pkg/context/user/application/update/command.handler.go b/pkg/context/user/application/update/command.handler.go new file mode 100644 index 0000000..71bc976 --- /dev/null +++ b/pkg/context/user/application/update/command.handler.go @@ -0,0 +1,42 @@ +package update + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/usecase" +) + +type Handler struct { + usecase.Update +} + +func (handler *Handler) Handle(command *Command) error { + account, err := user.New(&user.Primitive{ + Id: command.Id, + Email: command.Email, + Username: command.Username, + Password: command.Password, + }) + + if err != nil { + return errors.BubbleUp(err, "Handle") + } + + var updated *user.Password + + if command.UpdatedPassword != "" { + updated, err = user.NewPassword(command.UpdatedPassword) + + if err != nil { + return errors.BubbleUp(err, "Handle") + } + } + + err = handler.Update.Run(account, updated) + + if err != nil { + return errors.BubbleUp(err, "Handle") + } + + return nil +} diff --git a/pkg/context/user/application/update/command.handler_test.go b/pkg/context/user/application/update/command.handler_test.go new file mode 100644 index 0000000..e65561c --- /dev/null +++ b/pkg/context/user/application/update/command.handler_test.go @@ -0,0 +1,70 @@ +package update_test + +import ( + "testing" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/handlers" + "github.com/bastean/codexgo/v4/pkg/context/user/application/update" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/repository" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/usecase" + "github.com/bastean/codexgo/v4/pkg/context/user/infrastructure/cryptographic" + "github.com/bastean/codexgo/v4/pkg/context/user/infrastructure/persistence" + "github.com/stretchr/testify/suite" +) + +type UpdateTestSuite struct { + suite.Suite + sut handlers.Command[*update.Command] + update usecase.Update + hashing *cryptographic.HashingMock + repository *persistence.UserMock +} + +func (suite *UpdateTestSuite) SetupTest() { + suite.repository = new(persistence.UserMock) + + suite.hashing = new(cryptographic.HashingMock) + + suite.update = &update.Update{ + User: suite.repository, + Hashing: suite.hashing, + } + + suite.sut = &update.Handler{ + Update: suite.update, + } +} + +func (suite *UpdateTestSuite) TestUpdate() { + command := update.RandomCommand() + + new, _ := user.New(&user.Primitive{ + Id: command.Id, + Email: command.Email, + Username: command.Username, + Password: command.UpdatedPassword, + }) + + id, _ := user.NewId(command.Id) + + criteria := &repository.UserSearchCriteria{ + Id: id, + } + + suite.repository.On("Search", criteria).Return(new) + + suite.hashing.On("IsNotEqual", new.Password.Value, command.Password).Return(false) + + suite.repository.On("Update", new) + + suite.NoError(suite.sut.Handle(command)) + + suite.repository.AssertExpectations(suite.T()) + + suite.hashing.AssertExpectations(suite.T()) +} + +func TestUnitUpdateSuite(t *testing.T) { + suite.Run(t, new(UpdateTestSuite)) +} diff --git a/pkg/context/user/application/update/command.mother.go b/pkg/context/user/application/update/command.mother.go new file mode 100644 index 0000000..9b80a04 --- /dev/null +++ b/pkg/context/user/application/update/command.mother.go @@ -0,0 +1,21 @@ +package update + +import ( + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" +) + +func RandomCommand() *Command { + id := user.IdWithValidValue() + email := user.EmailWithValidValue() + username := user.UsernameWithValidValue() + password := user.PasswordWithValidValue() + updatedPassword := user.PasswordWithValidValue() + + return &Command{ + Id: id.Value, + Email: email.Value, + Username: username.Value, + Password: password.Value, + UpdatedPassword: updatedPassword.Value, + } +} diff --git a/pkg/context/user/application/update/update.go b/pkg/context/user/application/update/update.go new file mode 100644 index 0000000..c0cf9a2 --- /dev/null +++ b/pkg/context/user/application/update/update.go @@ -0,0 +1,44 @@ +package update + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/hashing" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/repository" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/service" +) + +type Update struct { + repository.User + hashing.Hashing +} + +func (update *Update) Run(account *user.User, updated *user.Password) error { + found, err := update.User.Search(&repository.UserSearchCriteria{ + Id: account.Id, + }) + + if err != nil { + return errors.BubbleUp(err, "Run") + } + + err = service.IsPasswordInvalid(update.Hashing, found.Password.Value, account.Password.Value) + + if err != nil { + return errors.BubbleUp(err, "Run") + } + + if updated != nil { + account.Password = updated + } + + account.Verified = found.Verified + + err = update.User.Update(account) + + if err != nil { + return errors.BubbleUp(err, "Run") + } + + return nil +} diff --git a/pkg/context/user/application/verify/command.go b/pkg/context/user/application/verify/command.go new file mode 100644 index 0000000..25b17f7 --- /dev/null +++ b/pkg/context/user/application/verify/command.go @@ -0,0 +1,5 @@ +package verify + +type Command struct { + Id string +} diff --git a/pkg/context/user/application/verify/command.handler.go b/pkg/context/user/application/verify/command.handler.go new file mode 100644 index 0000000..19c0e25 --- /dev/null +++ b/pkg/context/user/application/verify/command.handler.go @@ -0,0 +1,27 @@ +package verify + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/usecase" +) + +type Handler struct { + usecase.Verify +} + +func (handler *Handler) Handle(command *Command) error { + id, err := user.NewId(command.Id) + + if err != nil { + return errors.BubbleUp(err, "Handle") + } + + err = handler.Verify.Run(id) + + if err != nil { + return errors.BubbleUp(err, "Handle") + } + + return nil +} diff --git a/pkg/context/user/application/verify/command.handler_test.go b/pkg/context/user/application/verify/command.handler_test.go new file mode 100644 index 0000000..de64d22 --- /dev/null +++ b/pkg/context/user/application/verify/command.handler_test.go @@ -0,0 +1,58 @@ +package verify_test + +import ( + "testing" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/handlers" + "github.com/bastean/codexgo/v4/pkg/context/user/application/verify" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/repository" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/usecase" + "github.com/bastean/codexgo/v4/pkg/context/user/infrastructure/persistence" + "github.com/stretchr/testify/suite" +) + +type VerifyTestSuite struct { + suite.Suite + sut handlers.Command[*verify.Command] + verify usecase.Verify + repository *persistence.UserMock +} + +func (suite *VerifyTestSuite) SetupTest() { + suite.repository = new(persistence.UserMock) + + suite.verify = &verify.Verify{ + User: suite.repository, + } + + suite.sut = &verify.Handler{ + Verify: suite.verify, + } +} + +func (suite *VerifyTestSuite) TestVerify() { + command := verify.RandomCommand() + + random := user.Random() + + id, _ := user.NewId(command.Id) + + random.Id = id + + criteria := &repository.UserSearchCriteria{ + Id: id, + } + + suite.repository.On("Search", criteria).Return(random) + + suite.repository.On("Verify", id) + + suite.NoError(suite.sut.Handle(command)) + + suite.repository.AssertExpectations(suite.T()) +} + +func TestUnitVerifySuite(t *testing.T) { + suite.Run(t, new(VerifyTestSuite)) +} diff --git a/pkg/context/user/application/verify/command.mother.go b/pkg/context/user/application/verify/command.mother.go new file mode 100644 index 0000000..17ae432 --- /dev/null +++ b/pkg/context/user/application/verify/command.mother.go @@ -0,0 +1,13 @@ +package verify + +import ( + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" +) + +func RandomCommand() *Command { + id := user.IdWithValidValue() + + return &Command{ + Id: id.Value, + } +} diff --git a/pkg/context/user/application/verify/verify.go b/pkg/context/user/application/verify/verify.go new file mode 100644 index 0000000..4713c23 --- /dev/null +++ b/pkg/context/user/application/verify/verify.go @@ -0,0 +1,33 @@ +package verify + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/repository" +) + +type Verify struct { + repository.User +} + +func (verify *Verify) Run(id *user.Id) error { + found, err := verify.User.Search(&repository.UserSearchCriteria{ + Id: id, + }) + + if err != nil { + return errors.BubbleUp(err, "Run") + } + + if found.Verified.Value { + return nil + } + + err = verify.User.Verify(id) + + if err != nil { + return errors.BubbleUp(err, "Run") + } + + return nil +} diff --git a/pkg/context/user/domain/aggregate/user/created.go b/pkg/context/user/domain/aggregate/user/created.go new file mode 100644 index 0000000..1f78f8c --- /dev/null +++ b/pkg/context/user/domain/aggregate/user/created.go @@ -0,0 +1,42 @@ +package user + +import ( + "encoding/json" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages" +) + +var CreatedSucceededTypeRoutingKey = messages.NewRoutingKey(&messages.RoutingKeyComponents{ + Service: "user", + Version: "1", + Type: messages.Type.Event, + Entity: "user", + Event: "created", + Status: messages.Status.Succeeded, +}) + +type CreatedSucceededAttributes struct { + Id, Email, Username string +} + +type CreatedSucceeded struct { + Attributes *CreatedSucceededAttributes +} + +func NewCreatedSucceeded(event *CreatedSucceeded) (*messages.Message, error) { + attributes, err := json.Marshal(event.Attributes) + + if err != nil { + return nil, errors.NewInternal(&errors.Bubble{ + Where: "NewCreatedSucceeded", + What: "Failure to create event message attributes", + Why: errors.Meta{ + "Routing Key": CreatedSucceededTypeRoutingKey, + }, + Who: err, + }) + } + + return messages.New(CreatedSucceededTypeRoutingKey, attributes, messages.Meta{}), nil +} diff --git a/pkg/context/user/domain/aggregate/user/created.mother.go b/pkg/context/user/domain/aggregate/user/created.mother.go new file mode 100644 index 0000000..d3a0806 --- /dev/null +++ b/pkg/context/user/domain/aggregate/user/created.mother.go @@ -0,0 +1,26 @@ +package user + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/messages" +) + +func RandomCreatedSucceeded() *messages.Message { + id := IdWithValidValue() + email := EmailWithValidValue() + username := UsernameWithValidValue() + + event, err := NewCreatedSucceeded(&CreatedSucceeded{ + Attributes: &CreatedSucceededAttributes{ + Id: id.Value, + Email: email.Value, + Username: username.Value, + }, + }) + + if err != nil { + errors.Panic(err.Error(), "RandomCreatedSucceeded") + } + + return event +} diff --git a/pkg/context/user/domain/aggregate/user/email.go b/pkg/context/user/domain/aggregate/user/email.go new file mode 100644 index 0000000..75764f0 --- /dev/null +++ b/pkg/context/user/domain/aggregate/user/email.go @@ -0,0 +1,32 @@ +package user + +import ( + "strings" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/services" +) + +type Email struct { + Value string `validate:"email"` +} + +func NewEmail(value string) (*Email, error) { + value = strings.TrimSpace(value) + + valueObj := &Email{ + Value: value, + } + + if services.IsValueObjectInvalid(valueObj) { + return nil, errors.NewInvalidValue(&errors.Bubble{ + Where: "NewEmail", + What: "Invalid email format", + Why: errors.Meta{ + "Email": value, + }, + }) + } + + return valueObj, nil +} diff --git a/pkg/context/user/domain/aggregate/user/email.mother.go b/pkg/context/user/domain/aggregate/user/email.mother.go new file mode 100644 index 0000000..b7fd48f --- /dev/null +++ b/pkg/context/user/domain/aggregate/user/email.mother.go @@ -0,0 +1,24 @@ +package user + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/services" +) + +func EmailWithValidValue() *Email { + value, err := NewEmail(services.Create.Email()) + + if err != nil { + errors.Panic(err.Error(), "EmailWithValidValue") + } + + return value +} + +func EmailWithInvalidValue() (string, error) { + value := "x" + + _, err := NewEmail(value) + + return value, err +} diff --git a/pkg/context/user/domain/aggregate/user/email_test.go b/pkg/context/user/domain/aggregate/user/email_test.go new file mode 100644 index 0000000..2ccc969 --- /dev/null +++ b/pkg/context/user/domain/aggregate/user/email_test.go @@ -0,0 +1,38 @@ +package user_test + +import ( + "testing" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/stretchr/testify/suite" +) + +type EmailValueObjectTestSuite struct { + suite.Suite +} + +func (suite *EmailValueObjectTestSuite) SetupTest() {} + +func (suite *EmailValueObjectTestSuite) TestWithInvalidValue() { + value, err := user.EmailWithInvalidValue() + + var actual *errors.ErrInvalidValue + + suite.ErrorAs(err, &actual) + + expected := &errors.ErrInvalidValue{Bubble: &errors.Bubble{ + When: actual.When, + Where: "NewEmail", + What: "Invalid email format", + Why: errors.Meta{ + "Email": value, + }, + }} + + suite.EqualError(expected, actual.Error()) +} + +func TestUnitEmailValueObjectSuite(t *testing.T) { + suite.Run(t, new(EmailValueObjectTestSuite)) +} diff --git a/pkg/context/user/domain/aggregate/user/id.go b/pkg/context/user/domain/aggregate/user/id.go new file mode 100644 index 0000000..9e03e77 --- /dev/null +++ b/pkg/context/user/domain/aggregate/user/id.go @@ -0,0 +1,32 @@ +package user + +import ( + "strings" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/services" +) + +type Id struct { + Value string `validate:"uuid4"` +} + +func NewId(value string) (*Id, error) { + value = strings.TrimSpace(value) + + valueObj := &Id{ + Value: value, + } + + if services.IsValueObjectInvalid(valueObj) { + return nil, errors.NewInvalidValue(&errors.Bubble{ + Where: "NewId", + What: "Invalid UUID4 format", + Why: errors.Meta{ + "Id": value, + }, + }) + } + + return valueObj, nil +} diff --git a/pkg/context/user/domain/aggregate/user/id.mother.go b/pkg/context/user/domain/aggregate/user/id.mother.go new file mode 100644 index 0000000..c3f19e1 --- /dev/null +++ b/pkg/context/user/domain/aggregate/user/id.mother.go @@ -0,0 +1,24 @@ +package user + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/services" +) + +func IdWithValidValue() *Id { + value, err := NewId(services.Create.UUID()) + + if err != nil { + errors.Panic(err.Error(), "IdWithValidValue") + } + + return value +} + +func IdWithInvalidValue() (string, error) { + value := "x" + + _, err := NewId(value) + + return value, err +} diff --git a/pkg/context/user/domain/aggregate/user/id_test.go b/pkg/context/user/domain/aggregate/user/id_test.go new file mode 100644 index 0000000..2842963 --- /dev/null +++ b/pkg/context/user/domain/aggregate/user/id_test.go @@ -0,0 +1,38 @@ +package user_test + +import ( + "testing" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/stretchr/testify/suite" +) + +type IdValueObjectTestSuite struct { + suite.Suite +} + +func (suite *IdValueObjectTestSuite) SetupTest() {} + +func (suite *IdValueObjectTestSuite) TestWithInvalidValue() { + value, err := user.IdWithInvalidValue() + + var actual *errors.ErrInvalidValue + + suite.ErrorAs(err, &actual) + + expected := &errors.ErrInvalidValue{Bubble: &errors.Bubble{ + When: actual.When, + Where: "NewId", + What: "Invalid UUID4 format", + Why: errors.Meta{ + "Id": value, + }, + }} + + suite.EqualError(expected, actual.Error()) +} + +func TestUnitIdValueObjectSuite(t *testing.T) { + suite.Run(t, new(IdValueObjectTestSuite)) +} diff --git a/pkg/context/user/domain/aggregate/user/password.go b/pkg/context/user/domain/aggregate/user/password.go new file mode 100644 index 0000000..930a006 --- /dev/null +++ b/pkg/context/user/domain/aggregate/user/password.go @@ -0,0 +1,33 @@ +package user + +import ( + "fmt" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/services" +) + +const PasswordMinCharactersLength = "8" +const PasswordMaxCharactersLength = "64" + +type Password struct { + Value string `validate:"gte=8,lte=64"` +} + +func NewPassword(value string) (*Password, error) { + valueObj := &Password{ + Value: value, + } + + if services.IsValueObjectInvalid(valueObj) { + return nil, errors.NewInvalidValue(&errors.Bubble{ + Where: "NewPassword", + What: fmt.Sprintf("Password must be between %s to %s characters", PasswordMinCharactersLength, PasswordMaxCharactersLength), + Why: errors.Meta{ + "Password": value, + }, + }) + } + + return valueObj, nil +} diff --git a/pkg/context/user/domain/aggregate/user/password.mother.go b/pkg/context/user/domain/aggregate/user/password.mother.go new file mode 100644 index 0000000..199edc2 --- /dev/null +++ b/pkg/context/user/domain/aggregate/user/password.mother.go @@ -0,0 +1,24 @@ +package user + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/services" +) + +func PasswordWithValidValue() *Password { + value, err := NewPassword(services.Create.Regex(`^[\W\w]{8,64}$`)) + + if err != nil { + errors.Panic(err.Error(), "PasswordWithValidValue") + } + + return value +} + +func PasswordWithInvalidLength() (string, error) { + value := "x" + + _, err := NewPassword(value) + + return value, err +} diff --git a/pkg/context/user/domain/aggregate/user/password_test.go b/pkg/context/user/domain/aggregate/user/password_test.go new file mode 100644 index 0000000..fc82fc4 --- /dev/null +++ b/pkg/context/user/domain/aggregate/user/password_test.go @@ -0,0 +1,38 @@ +package user_test + +import ( + "testing" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/stretchr/testify/suite" +) + +type PasswordValueObjectTestSuite struct { + suite.Suite +} + +func (suite *PasswordValueObjectTestSuite) SetupTest() {} + +func (suite *PasswordValueObjectTestSuite) TestWithInvalidLength() { + value, err := user.PasswordWithInvalidLength() + + var actual *errors.ErrInvalidValue + + suite.ErrorAs(err, &actual) + + expected := &errors.ErrInvalidValue{Bubble: &errors.Bubble{ + When: actual.When, + Where: "NewPassword", + What: "Password must be between 8 to 64 characters", + Why: errors.Meta{ + "Password": value, + }, + }} + + suite.EqualError(expected, actual.Error()) +} + +func TestUnitPasswordValueObjectSuite(t *testing.T) { + suite.Run(t, new(PasswordValueObjectTestSuite)) +} diff --git a/pkg/context/user/domain/aggregate/user/user.mother.go b/pkg/context/user/domain/aggregate/user/user.mother.go new file mode 100644 index 0000000..3cdc76d --- /dev/null +++ b/pkg/context/user/domain/aggregate/user/user.mother.go @@ -0,0 +1,29 @@ +package user + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" +) + +func Random() *User { + id := IdWithValidValue() + email := EmailWithValidValue() + username := UsernameWithValidValue() + password := PasswordWithValidValue() + + user, err := New(&Primitive{ + Id: id.Value, + Email: email.Value, + Username: username.Value, + Password: password.Value, + }) + + if err != nil { + errors.Panic(err.Error(), "Random") + } + + return user +} + +func RandomPrimitive() *Primitive { + return Random().ToPrimitive() +} diff --git a/pkg/context/user/domain/aggregate/user/user.root.go b/pkg/context/user/domain/aggregate/user/user.root.go new file mode 100644 index 0000000..1906248 --- /dev/null +++ b/pkg/context/user/domain/aggregate/user/user.root.go @@ -0,0 +1,89 @@ +package user + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/aggregates" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" +) + +type User struct { + *aggregates.Root + *Id + *Email + *Username + *Password + *Verified +} + +type Primitive struct { + Id, Email, Username, Password string + Verified bool +} + +func create(primitive *Primitive) (*User, error) { + root := aggregates.NewRoot() + + id, errId := NewId(primitive.Id) + email, errEmail := NewEmail(primitive.Email) + username, errUsername := NewUsername(primitive.Username) + password, errPassword := NewPassword(primitive.Password) + verified, errVerified := NewVerified(primitive.Verified) + + if err := errors.Join(errId, errEmail, errUsername, errPassword, errVerified); err != nil { + return nil, errors.BubbleUp(err, "create") + } + + return &User{ + Root: root, + Id: id, + Email: email, + Username: username, + Password: password, + Verified: verified, + }, nil +} + +func (user *User) ToPrimitive() *Primitive { + return &Primitive{ + Id: user.Id.Value, + Email: user.Email.Value, + Username: user.Username.Value, + Password: user.Password.Value, + Verified: user.Verified.Value, + } +} + +func FromPrimitive(primitive *Primitive) (*User, error) { + user, err := create(primitive) + + if err != nil { + return nil, errors.BubbleUp(err, "FromPrimitive") + } + + return user, nil +} + +func New(primitive *Primitive) (*User, error) { + primitive.Verified = false + + user, err := create(primitive) + + if err != nil { + return nil, errors.BubbleUp(err, "New") + } + + message, err := NewCreatedSucceeded(&CreatedSucceeded{ + Attributes: &CreatedSucceededAttributes{ + Id: user.Id.Value, + Email: user.Email.Value, + Username: user.Username.Value, + }, + }) + + if err != nil { + return nil, errors.BubbleUp(err, "New") + } + + user.RecordMessage(message) + + return user, nil +} diff --git a/pkg/context/user/domain/aggregate/user/username.go b/pkg/context/user/domain/aggregate/user/username.go new file mode 100644 index 0000000..aebbe6b --- /dev/null +++ b/pkg/context/user/domain/aggregate/user/username.go @@ -0,0 +1,36 @@ +package user + +import ( + "fmt" + "strings" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/services" +) + +const UsernameMinCharactersLength = "2" +const UsernameMaxCharactersLength = "20" + +type Username struct { + Value string `validate:"gte=2,lte=20,alphanum"` +} + +func NewUsername(value string) (*Username, error) { + value = strings.TrimSpace(value) + + valueObj := &Username{ + Value: value, + } + + if services.IsValueObjectInvalid(valueObj) { + return nil, errors.NewInvalidValue(&errors.Bubble{ + Where: "NewUsername", + What: fmt.Sprintf("Username must be between %s to %s characters and be alphanumeric only", UsernameMinCharactersLength, UsernameMaxCharactersLength), + Why: errors.Meta{ + "Username": value, + }, + }) + } + + return valueObj, nil +} diff --git a/pkg/context/user/domain/aggregate/user/username.mother.go b/pkg/context/user/domain/aggregate/user/username.mother.go new file mode 100644 index 0000000..9bdfc2c --- /dev/null +++ b/pkg/context/user/domain/aggregate/user/username.mother.go @@ -0,0 +1,32 @@ +package user + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/services" +) + +func UsernameWithValidValue() *Username { + value, err := NewUsername(services.Create.Regex(`^[A-Za-z0-9]{2,20}$`)) + + if err != nil { + errors.Panic(err.Error(), "UsernameWithValidValue") + } + + return value +} + +func UsernameWithInvalidLength() (string, error) { + value := "x" + + _, err := NewUsername(value) + + return value, err +} + +func UsernameWithInvalidAlphanumeric() (string, error) { + value := "<>" + + _, err := NewUsername(value) + + return value, err +} diff --git a/pkg/context/user/domain/aggregate/user/username_test.go b/pkg/context/user/domain/aggregate/user/username_test.go new file mode 100644 index 0000000..0df6e31 --- /dev/null +++ b/pkg/context/user/domain/aggregate/user/username_test.go @@ -0,0 +1,57 @@ +package user_test + +import ( + "testing" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/stretchr/testify/suite" +) + +type UsernameValueObjectTestSuite struct { + suite.Suite +} + +func (suite *UsernameValueObjectTestSuite) SetupTest() {} + +func (suite *UsernameValueObjectTestSuite) TestWithInvalidLength() { + value, err := user.UsernameWithInvalidLength() + + var actual *errors.ErrInvalidValue + + suite.ErrorAs(err, &actual) + + expected := &errors.ErrInvalidValue{Bubble: &errors.Bubble{ + When: actual.When, + Where: "NewUsername", + What: "Username must be between 2 to 20 characters and be alphanumeric only", + Why: errors.Meta{ + "Username": value, + }, + }} + + suite.EqualError(expected, actual.Error()) +} + +func (suite *UsernameValueObjectTestSuite) TestWithInvalidAlphanumeric() { + value, err := user.UsernameWithInvalidAlphanumeric() + + var actual *errors.ErrInvalidValue + + suite.ErrorAs(err, &actual) + + expected := &errors.ErrInvalidValue{Bubble: &errors.Bubble{ + When: actual.When, + Where: "NewUsername", + What: "Username must be between 2 to 20 characters and be alphanumeric only", + Why: errors.Meta{ + "Username": value, + }, + }} + + suite.EqualError(expected, actual.Error()) +} + +func TestUnitUsernameValueObjectSuite(t *testing.T) { + suite.Run(t, new(UsernameValueObjectTestSuite)) +} diff --git a/pkg/context/user/domain/aggregate/user/verified.go b/pkg/context/user/domain/aggregate/user/verified.go new file mode 100644 index 0000000..b6c0958 --- /dev/null +++ b/pkg/context/user/domain/aggregate/user/verified.go @@ -0,0 +1,30 @@ +package user + +import ( + "fmt" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/services" +) + +type Verified struct { + Value bool +} + +func NewVerified(value bool) (*Verified, error) { + valueObj := &Verified{ + Value: value, + } + + if services.IsValueObjectInvalid(valueObj) { + return nil, errors.NewInvalidValue(&errors.Bubble{ + Where: "NewVerified", + What: "Invalid verified value", + Why: errors.Meta{ + "Verified": fmt.Sprintf("%t", value), + }, + }) + } + + return valueObj, nil +} diff --git a/pkg/context/user/domain/hashing/hashing.go b/pkg/context/user/domain/hashing/hashing.go new file mode 100644 index 0000000..1e9f0f9 --- /dev/null +++ b/pkg/context/user/domain/hashing/hashing.go @@ -0,0 +1,6 @@ +package hashing + +type Hashing interface { + Hash(plain string) (string, error) + IsNotEqual(hashed, plain string) bool +} diff --git a/pkg/context/user/domain/repository/user.go b/pkg/context/user/domain/repository/user.go new file mode 100644 index 0000000..80a15a3 --- /dev/null +++ b/pkg/context/user/domain/repository/user.go @@ -0,0 +1,18 @@ +package repository + +import ( + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" +) + +type UserSearchCriteria struct { + *user.Id + *user.Email +} + +type User interface { + Save(*user.User) error + Verify(*user.Id) error + Update(*user.User) error + Delete(*user.Id) error + Search(*UserSearchCriteria) (*user.User, error) +} diff --git a/pkg/context/user/domain/service/password.go b/pkg/context/user/domain/service/password.go new file mode 100644 index 0000000..a45bab7 --- /dev/null +++ b/pkg/context/user/domain/service/password.go @@ -0,0 +1,17 @@ +package service + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/hashing" +) + +func IsPasswordInvalid(hashing hashing.Hashing, hashed, plain string) error { + if hashing.IsNotEqual(hashed, plain) { + return errors.NewFailure(&errors.Bubble{ + Where: "IsPasswordInvalid", + What: "Invalid password", + }) + } + + return nil +} diff --git a/pkg/context/user/domain/usecase/usecase.go b/pkg/context/user/domain/usecase/usecase.go new file mode 100644 index 0000000..9d94e9f --- /dev/null +++ b/pkg/context/user/domain/usecase/usecase.go @@ -0,0 +1,32 @@ +package usecase + +import ( + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" +) + +type ( + Create interface { + Run(*user.User) error + } + Read interface { + Run(*user.Id) (*user.User, error) + } + Update interface { + Run(*user.User, *user.Password) error + } + Delete interface { + Run(*user.Id, *user.Password) error + } + Verify interface { + Run(*user.Id) error + } + Login interface { + Run(*user.Email, *user.Password) (*user.User, error) + } +) + +type ( + Created interface { + Run(*user.CreatedSucceeded) error + } +) diff --git a/pkg/context/user/infrastructure/cryptographic/bcrypt/bcrypt.go b/pkg/context/user/infrastructure/cryptographic/bcrypt/bcrypt.go new file mode 100644 index 0000000..675cfef --- /dev/null +++ b/pkg/context/user/infrastructure/cryptographic/bcrypt/bcrypt.go @@ -0,0 +1,27 @@ +package bcrypt + +import ( + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "golang.org/x/crypto/bcrypt" +) + +type Bcrypt struct{} + +func (*Bcrypt) Hash(plain string) (string, error) { + salt := 10 + hashed, err := bcrypt.GenerateFromPassword([]byte(plain), salt) + + if err != nil { + return "", errors.NewInternal(&errors.Bubble{ + Where: "Hash", + What: "Failure to generate a hash", + Who: err, + }) + } + + return string(hashed), nil +} + +func (*Bcrypt) IsNotEqual(hashed, plain string) bool { + return bcrypt.CompareHashAndPassword([]byte(hashed), []byte(plain)) != nil +} diff --git a/pkg/context/user/infrastructure/cryptographic/bcrypt/bcrypt_test.go b/pkg/context/user/infrastructure/cryptographic/bcrypt/bcrypt_test.go new file mode 100644 index 0000000..f41c910 --- /dev/null +++ b/pkg/context/user/infrastructure/cryptographic/bcrypt/bcrypt_test.go @@ -0,0 +1,49 @@ +package bcrypt_test + +import ( + "testing" + + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/hashing" + "github.com/bastean/codexgo/v4/pkg/context/user/infrastructure/cryptographic/bcrypt" + "github.com/stretchr/testify/suite" +) + +type BcryptTestSuite struct { + suite.Suite + sut hashing.Hashing +} + +func (suite *BcryptTestSuite) SetupTest() { + suite.sut = new(bcrypt.Bcrypt) +} + +func (suite *BcryptTestSuite) TestHash() { + password := user.PasswordWithValidValue() + + plain := password.Value + + hashed, err := suite.sut.Hash(plain) + + suite.NoError(err) + + suite.NotEqual(plain, hashed) +} + +func (suite *BcryptTestSuite) TestIsNotEqual() { + password := user.PasswordWithValidValue() + + plain := password.Value + + hashed, err := suite.sut.Hash(plain) + + suite.NoError(err) + + isNotEqual := suite.sut.IsNotEqual(hashed, plain) + + suite.False(isNotEqual) +} + +func TestIntegrationBcryptSuite(t *testing.T) { + suite.Run(t, new(BcryptTestSuite)) +} diff --git a/pkg/context/user/infrastructure/cryptographic/hashing.mock.go b/pkg/context/user/infrastructure/cryptographic/hashing.mock.go new file mode 100644 index 0000000..4c6ca84 --- /dev/null +++ b/pkg/context/user/infrastructure/cryptographic/hashing.mock.go @@ -0,0 +1,19 @@ +package cryptographic + +import ( + "github.com/stretchr/testify/mock" +) + +type HashingMock struct { + mock.Mock +} + +func (hashing *HashingMock) Hash(plain string) (string, error) { + args := hashing.Called(plain) + return args.Get(0).(string), nil +} + +func (hashing *HashingMock) IsNotEqual(hashed, plain string) bool { + args := hashing.Called(hashed, plain) + return args.Get(0).(bool) +} diff --git a/pkg/context/user/infrastructure/persistence/collection/user.go b/pkg/context/user/infrastructure/persistence/collection/user.go new file mode 100644 index 0000000..3272ce4 --- /dev/null +++ b/pkg/context/user/infrastructure/persistence/collection/user.go @@ -0,0 +1,215 @@ +package collection + +import ( + "context" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/infrastructure/persistences/mongodb" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/hashing" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/repository" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +type UserDocument struct { + Id string `bson:"id,omitempty"` + Email string `bson:"email,omitempty"` + Username string `bson:"username,omitempty"` + Password string `bson:"password,omitempty"` + Verified bool `bson:"verified,omitempty"` +} + +type User struct { + *mongo.Collection + hashing.Hashing +} + +func (mongoDB *User) Save(user *user.User) error { + new := UserDocument(*user.ToPrimitive()) + + hashed, err := mongoDB.Hashing.Hash(new.Password) + + if err != nil { + return errors.BubbleUp(err, "Save") + } + + new.Password = hashed + + _, err = mongoDB.Collection.InsertOne(context.Background(), &new) + + if mongo.IsDuplicateKeyError(err) { + return errors.BubbleUp(mongodb.HandleDuplicateKeyError(err), "Save") + } + + if err != nil { + return errors.NewInternal(&errors.Bubble{ + Where: "Save", + What: "Failure to save a user", + Why: errors.Meta{ + "Id": user.Id.Value, + }, + Who: err, + }) + } + + return nil +} + +func (mongoDB *User) Verify(id *user.Id) error { + filter := bson.D{{Key: "id", Value: id.Value}} + + _, err := mongoDB.Collection.UpdateOne(context.Background(), filter, bson.D{ + {Key: "$set", Value: bson.D{ + {Key: "verified", Value: true}, + }}, + }) + + if err != nil { + return errors.NewInternal(&errors.Bubble{ + Where: "Verify", + What: "Failure to verify a user", + Why: errors.Meta{ + "Id": id.Value, + }, + Who: err, + }) + } + + return nil +} + +func (mongoDB *User) Update(user *user.User) error { + updated := UserDocument(*user.ToPrimitive()) + + filter := bson.D{{Key: "id", Value: user.Id.Value}} + + hashed, err := mongoDB.Hashing.Hash(user.Password.Value) + + if err != nil { + return errors.BubbleUp(err, "Update") + } + + updated.Password = hashed + + _, err = mongoDB.Collection.ReplaceOne(context.Background(), filter, &updated) + + if err != nil { + return errors.NewInternal(&errors.Bubble{ + Where: "Update", + What: "Failure to update a user", + Why: errors.Meta{ + "Id": user.Id.Value, + }, + Who: err, + }) + } + + return nil +} + +func (mongoDB *User) Delete(id *user.Id) error { + filter := bson.D{{Key: "id", Value: id.Value}} + + _, err := mongoDB.Collection.DeleteOne(context.Background(), filter) + + if err != nil { + return errors.NewInternal(&errors.Bubble{ + Where: "Delete", + What: "Failure to delete a user", + Why: errors.Meta{ + "Id": id.Value, + }, + Who: err, + }) + } + + return nil +} + +func (mongoDB *User) Search(criteria *repository.UserSearchCriteria) (*user.User, error) { + var filter bson.D + var index string + + switch { + case criteria.Id != nil: + filter = bson.D{{Key: "id", Value: criteria.Id.Value}} + index = criteria.Id.Value + case criteria.Email != nil: + filter = bson.D{{Key: "email", Value: criteria.Email.Value}} + index = criteria.Email.Value + } + + result := mongoDB.Collection.FindOne(context.Background(), filter) + + if err := result.Err(); err != nil { + return nil, mongodb.HandleDocumentNotFound(index, err) + } + + primitive := new(user.Primitive) + + err := result.Decode(primitive) + + if err != nil { + return nil, errors.NewInternal(&errors.Bubble{ + Where: "Search", + What: "Failure to decode a result", + Why: errors.Meta{ + "Index": index, + }, + Who: err, + }) + } + + found, err := user.FromPrimitive(primitive) + + if err != nil { + return nil, errors.NewInternal(&errors.Bubble{ + Where: "Search", + What: "Failure to create an user from a primitive", + Why: errors.Meta{ + "Primitive": primitive, + "Index": index, + }, + Who: err, + }) + } + + return found, nil +} + +func OpenUser(session *mongodb.MongoDB, name string, hashing hashing.Hashing) (repository.User, error) { + collection := session.Database.Collection(name) + + _, err := collection.Indexes().CreateMany(context.Background(), []mongo.IndexModel{ + { + Keys: bson.D{{Key: "id", Value: 1}}, + Options: options.Index().SetUnique(true), + }, + { + Keys: bson.D{{Key: "email", Value: 1}}, + Options: options.Index().SetUnique(true), + }, + { + Keys: bson.D{{Key: "username", Value: 1}}, + Options: options.Index().SetUnique(true), + }, + }) + + if err != nil { + return nil, errors.NewInternal(&errors.Bubble{ + Where: "OpenUser", + What: "Failure to create indexes for user collection", + Why: errors.Meta{ + "Collection": name, + }, + Who: err, + }) + } + + return &User{ + Collection: collection, + Hashing: hashing, + }, nil +} diff --git a/pkg/context/user/infrastructure/persistence/collection/user_test.go b/pkg/context/user/infrastructure/persistence/collection/user_test.go new file mode 100644 index 0000000..c5cf0ca --- /dev/null +++ b/pkg/context/user/infrastructure/persistence/collection/user_test.go @@ -0,0 +1,200 @@ +package collection_test + +import ( + "fmt" + "os" + "testing" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/infrastructure/persistences/mongodb" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/repository" + "github.com/bastean/codexgo/v4/pkg/context/user/infrastructure/cryptographic" + "github.com/bastean/codexgo/v4/pkg/context/user/infrastructure/persistence/collection" + "github.com/stretchr/testify/suite" +) + +type UserTestSuite struct { + suite.Suite + sut repository.User + hashing *cryptographic.HashingMock +} + +func (suite *UserTestSuite) SetupTest() { + session, _ := mongodb.Open( + os.Getenv("DATABASE_MONGODB_URI"), + os.Getenv("DATABASE_MONGODB_NAME"), + ) + + name := "users-test" + + suite.hashing = new(cryptographic.HashingMock) + + suite.sut, _ = collection.OpenUser(session, name, suite.hashing) +} + +func (suite *UserTestSuite) TestSave() { + expected := user.Random() + + expected.PullMessages() + + suite.hashing.On("Hash", expected.Password.Value).Return(expected.Password.Value) + + suite.NoError(suite.sut.Save(expected)) + + suite.hashing.AssertExpectations(suite.T()) + + criteria := &repository.UserSearchCriteria{ + Id: expected.Id, + } + + actual, err := suite.sut.Search(criteria) + + suite.NoError(err) + + suite.Equal(expected, actual) +} + +func (suite *UserTestSuite) TestSaveErrDuplicateKey() { + random := user.Random() + + suite.hashing.On("Hash", random.Password.Value).Return(random.Password.Value) + + suite.NoError(suite.sut.Save(random)) + + err := suite.sut.Save(random) + + suite.hashing.AssertExpectations(suite.T()) + + var actual *errors.ErrAlreadyExist + + suite.ErrorAs(err, &actual) + + expected := &errors.ErrAlreadyExist{Bubble: &errors.Bubble{ + When: actual.When, + Where: "HandleDuplicateKeyError", + What: "Id already registered", + Why: errors.Meta{ + "Field": "Id", + }, + Who: actual.Who, + }} + + suite.EqualError(expected, actual.Error()) +} + +func (suite *UserTestSuite) TestVerify() { + random := user.Random() + + suite.hashing.On("Hash", random.Password.Value).Return(random.Password.Value) + + suite.NoError(suite.sut.Save(random)) + + suite.NoError(suite.sut.Verify(random.Id)) + + criteria := &repository.UserSearchCriteria{ + Id: random.Id, + } + + actual, err := suite.sut.Search(criteria) + + suite.NoError(err) + + suite.True(actual.Verified.Value) +} + +func (suite *UserTestSuite) TestUpdate() { + expected := user.Random() + + expected.PullMessages() + + suite.hashing.On("Hash", expected.Password.Value).Return(expected.Password.Value) + + suite.NoError(suite.sut.Save(expected)) + + expected.Password = user.PasswordWithValidValue() + + suite.hashing.On("Hash", expected.Password.Value).Return(expected.Password.Value) + + suite.NoError(suite.sut.Update(expected)) + + suite.hashing.AssertExpectations(suite.T()) + + criteria := &repository.UserSearchCriteria{ + Id: expected.Id, + } + + actual, err := suite.sut.Search(criteria) + + suite.NoError(err) + + suite.Equal(expected, actual) +} + +func (suite *UserTestSuite) TestDelete() { + random := user.Random() + + suite.hashing.On("Hash", random.Password.Value).Return(random.Password.Value) + + suite.NoError(suite.sut.Save(random)) + + suite.NoError(suite.sut.Delete(random.Id)) + + criteria := &repository.UserSearchCriteria{ + Id: random.Id, + } + + _, err := suite.sut.Search(criteria) + + suite.Error(err) +} + +func (suite *UserTestSuite) TestSearch() { + expected := user.Random() + + expected.PullMessages() + + suite.hashing.On("Hash", expected.Password.Value).Return(expected.Password.Value) + + suite.NoError(suite.sut.Save(expected)) + + criteria := &repository.UserSearchCriteria{ + Id: expected.Id, + } + + actual, err := suite.sut.Search(criteria) + + suite.NoError(err) + + suite.Equal(expected, actual) +} + +func (suite *UserTestSuite) TestSearchErrDocumentNotFound() { + random := user.Random() + + criteria := &repository.UserSearchCriteria{ + Id: random.Id, + } + + _, err := suite.sut.Search(criteria) + + var actual *errors.ErrNotExist + + suite.ErrorAs(err, &actual) + + expected := &errors.ErrNotExist{Bubble: &errors.Bubble{ + When: actual.When, + Where: "HandleDocumentNotFound", + What: fmt.Sprintf("%s not found", random.Id.Value), + Why: errors.Meta{ + "Index": random.Id.Value, + }, + Who: actual.Who, + }} + + suite.EqualError(expected, actual.Error()) +} + +func TestIntegrationUserSuite(t *testing.T) { + suite.Run(t, new(UserTestSuite)) +} diff --git a/pkg/context/user/infrastructure/persistence/user.mock.go b/pkg/context/user/infrastructure/persistence/user.mock.go new file mode 100644 index 0000000..d45d420 --- /dev/null +++ b/pkg/context/user/infrastructure/persistence/user.mock.go @@ -0,0 +1,36 @@ +package persistence + +import ( + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/repository" + "github.com/stretchr/testify/mock" +) + +type UserMock struct { + mock.Mock +} + +func (repository *UserMock) Save(user *user.User) error { + repository.Called(user) + return nil +} + +func (repository *UserMock) Verify(id *user.Id) error { + repository.Called(id) + return nil +} + +func (repository *UserMock) Update(user *user.User) error { + repository.Called(user) + return nil +} + +func (repository *UserMock) Delete(id *user.Id) error { + repository.Called(id) + return nil +} + +func (repository *UserMock) Search(criteria *repository.UserSearchCriteria) (*user.User, error) { + args := repository.Called(criteria) + return args.Get(0).(*user.User), nil +} diff --git a/pkg/context/user/infrastructure/transport/mail/confirmation.go b/pkg/context/user/infrastructure/transport/mail/confirmation.go new file mode 100644 index 0000000..fed7c0b --- /dev/null +++ b/pkg/context/user/infrastructure/transport/mail/confirmation.go @@ -0,0 +1,68 @@ +package mail + +import ( + "bytes" + "context" + "fmt" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/infrastructure/transports/smtp" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" +) + +type Confirmation struct { + *smtp.SMTP +} + +func (client *Confirmation) Submit(data any) error { + attributes, ok := data.(*user.CreatedSucceededAttributes) + + if !ok { + return errors.NewInternal(&errors.Bubble{ + Where: "Submit", + What: "Failure in type assertion", + Why: errors.Meta{ + "Expected": new(user.CreatedSucceededAttributes), + "Actual": data, + }, + }) + } + + var message bytes.Buffer + + headers := client.Headers(attributes.Email, "Account Confirmation") + + _, err := message.Write([]byte(headers)) + + if err != nil { + return errors.NewInternal(&errors.Bubble{ + Where: "Submit", + What: "Failure to write message headers", + Why: errors.Meta{ + "Headers": headers, + "User Id": attributes.Id, + }, + Who: err, + }) + } + + link := fmt.Sprintf("%s/v4/account/verify/%s", client.ServerURL, attributes.Id) + + ConfirmationTemplate(attributes.Username, link).Render(context.Background(), &message) + + err = client.SendMail([]string{attributes.Email}, message.Bytes()) + + if err != nil { + return errors.NewInternal(&errors.Bubble{ + Where: "Submit", + What: "Failure to send an account confirmation mail", + Why: errors.Meta{ + "User Id": attributes.Id, + "SMTP Server URL": client.SMTPServerURL, + }, + Who: err, + }) + } + + return nil +} diff --git a/pkg/context/user/infrastructure/transport/mail/confirmation.templ b/pkg/context/user/infrastructure/transport/mail/confirmation.templ new file mode 100644 index 0000000..41daaa6 --- /dev/null +++ b/pkg/context/user/infrastructure/transport/mail/confirmation.templ @@ -0,0 +1,48 @@ +package mail + +templ ConfirmationTemplate(username, link string) { +
+
+
+ +
+
+

Hi { username }

+

+ Please confirm your account by clicking the button below +

+ + CONFIRM + + +
+
+
+} diff --git a/pkg/context/user/infrastructure/transport/mail/confirmation_test.go b/pkg/context/user/infrastructure/transport/mail/confirmation_test.go new file mode 100644 index 0000000..8ef65de --- /dev/null +++ b/pkg/context/user/infrastructure/transport/mail/confirmation_test.go @@ -0,0 +1,49 @@ +package mail_test + +import ( + "encoding/json" + "os" + "testing" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/transfers" + "github.com/bastean/codexgo/v4/pkg/context/shared/infrastructure/transports/smtp" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/bastean/codexgo/v4/pkg/context/user/infrastructure/transport/mail" + "github.com/stretchr/testify/suite" +) + +type ConfirmationTestSuite struct { + suite.Suite + sut transfers.Transfer + smtp *smtp.SMTP +} + +func (suite *ConfirmationTestSuite) SetupTest() { + suite.smtp = smtp.Open( + os.Getenv("CODEXGO_SMTP_HOST"), + os.Getenv("CODEXGO_SMTP_PORT"), + os.Getenv("CODEXGO_SMTP_USERNAME"), + os.Getenv("CODEXGO_SMTP_PASSWORD"), + os.Getenv("CODEXGO_SERVER_GIN_URL"), + ) + + suite.sut = &mail.Confirmation{ + SMTP: suite.smtp, + } +} + +func (suite *ConfirmationTestSuite) TestSubmit() { + message := user.RandomCreatedSucceeded() + + event := new(user.CreatedSucceeded) + + event.Attributes = new(user.CreatedSucceededAttributes) + + suite.NoError(json.Unmarshal(message.Attributes, event.Attributes)) + + suite.NoError(suite.sut.Submit(event.Attributes)) +} + +func TestIntegrationConfirmationSuite(t *testing.T) { + suite.Run(t, new(ConfirmationTestSuite)) +} diff --git a/pkg/context/user/infrastructure/transport/terminal/confirmation.go b/pkg/context/user/infrastructure/transport/terminal/confirmation.go new file mode 100644 index 0000000..c565176 --- /dev/null +++ b/pkg/context/user/infrastructure/transport/terminal/confirmation.go @@ -0,0 +1,35 @@ +package terminal + +import ( + "fmt" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/errors" + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/loggers" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" +) + +type Confirmation struct { + loggers.Logger + ServerURL string +} + +func (client *Confirmation) Submit(data any) error { + attributes, ok := data.(*user.CreatedSucceededAttributes) + + if !ok { + return errors.NewInternal(&errors.Bubble{ + Where: "Submit", + What: "Failure in type assertion", + Why: errors.Meta{ + "Expected": new(user.CreatedSucceededAttributes), + "Actual": data, + }, + }) + } + + link := fmt.Sprintf("Hi %s, please confirm your account through this link: %s/v4/account/verify/%s", attributes.Username, client.ServerURL, attributes.Id) + + client.Logger.Info(link) + + return nil +} diff --git a/pkg/context/user/infrastructure/transport/terminal/confirmation_test.go b/pkg/context/user/infrastructure/transport/terminal/confirmation_test.go new file mode 100644 index 0000000..a0b9cec --- /dev/null +++ b/pkg/context/user/infrastructure/transport/terminal/confirmation_test.go @@ -0,0 +1,54 @@ +package terminal_test + +import ( + "encoding/json" + "fmt" + "os" + "testing" + + "github.com/bastean/codexgo/v4/pkg/context/shared/domain/transfers" + "github.com/bastean/codexgo/v4/pkg/context/shared/infrastructure/records" + "github.com/bastean/codexgo/v4/pkg/context/user/domain/aggregate/user" + "github.com/bastean/codexgo/v4/pkg/context/user/infrastructure/transport/terminal" + "github.com/stretchr/testify/suite" +) + +type ConfirmationTestSuite struct { + suite.Suite + sut transfers.Transfer + logger *records.LoggerMock + serverURL string +} + +func (suite *ConfirmationTestSuite) SetupTest() { + suite.logger = new(records.LoggerMock) + + suite.serverURL = os.Getenv("CODEXGO_SERVER_GIN_URL") + + suite.sut = &terminal.Confirmation{ + Logger: suite.logger, + ServerURL: suite.serverURL, + } +} + +func (suite *ConfirmationTestSuite) TestSubmit() { + message := user.RandomCreatedSucceeded() + + event := new(user.CreatedSucceeded) + + event.Attributes = new(user.CreatedSucceededAttributes) + + suite.NoError(json.Unmarshal(message.Attributes, event.Attributes)) + + link := fmt.Sprintf("Hi %s, please confirm your account through this link: %s/v4/account/verify/%s", event.Attributes.Username, suite.serverURL, event.Attributes.Id) + + suite.logger.Mock.On("Info", link) + + suite.NoError(suite.sut.Submit(event.Attributes)) + + suite.logger.AssertExpectations(suite.T()) +} + +func TestIntegrationConfirmationSuite(t *testing.T) { + suite.Run(t, new(ConfirmationTestSuite)) +} diff --git a/scripts/copydeps/copydeps.go b/scripts/copydeps/copydeps.go new file mode 100644 index 0000000..6ac67f6 --- /dev/null +++ b/scripts/copydeps/copydeps.go @@ -0,0 +1,103 @@ +package main + +import ( + "errors" + "fmt" + "log" + "os" + "path/filepath" + "regexp" + "strings" +) + +const regExpEveryMinFile = `^.+\.min\.(js|css)$` +const regExpEveryWoff2File = `^.+\.woff2$` + +const staticPath = "internal/app/server/static/dist" + +const jquerySourcePath = "node_modules/jquery/dist" +const jqueryStaticPath = staticPath + "/jquery.com" + +const fomanticSourcePath = "node_modules/fomantic-ui/dist" +const fomanticStaticPath = staticPath + "/fomantic-ui.com" + +const lodashSourcePath = "node_modules/lodash" +const lodashStaticPath = staticPath + "/lodash.com" + +func Panic(who error, what, where string) { + log.Panicf("(%s): %s: [%s]", where, what, who) +} + +func CreateDirectory(path string) { + err := os.MkdirAll(path, os.ModePerm) + + if err != nil { + Panic(err, fmt.Sprintf("failed to create \"%s\"", path), "CreateDirectory") + } + + log.Printf("created: \"%s\"", path) +} + +func CopyFile(filename, sourcePath, targetPath string) { + data, err := os.ReadFile(filepath.Join(sourcePath, filepath.Base(filename))) + + if err != nil { + Panic(err, fmt.Sprintf("failed to read \"%s\" from \"%s\"", filename, sourcePath), "CopyFile") + } + + err = os.WriteFile(filepath.Join(targetPath, filepath.Base(filename)), data, os.ModePerm) + + if err != nil { + Panic(err, fmt.Sprintf("failed to write \"%s\" on \"%s\"", filename, targetPath), "CopyFile") + } + + log.Printf("created: \"%s\"", filepath.Join(targetPath, filepath.Base(filename))) +} + +func CopyDeps(filenames []string, sourcePath, targetPath string) { + files, err := os.ReadDir(sourcePath) + + if err != nil { + Panic(err, fmt.Sprintf("failed to copy \"%s\" from \"%s\"", filenames, sourcePath), "CopyDeps") + } + + CreateDirectory(targetPath) + + if strings.HasPrefix(filenames[0], "^") && strings.HasSuffix(filenames[0], "$") { + isMinFile := regexp.MustCompile(filenames[0]).MatchString + + for _, file := range files { + if isMinFile(file.Name()) { + CopyFile(file.Name(), sourcePath, targetPath) + } + } + + return + } + + for _, filename := range filenames { + for _, file := range files { + if filepath.Base(filename) == file.Name() { + CopyFile(filename, sourcePath, targetPath) + } + } + } +} + +func main() { + err := os.RemoveAll(staticPath) + + if err != nil && !errors.Is(err, os.ErrNotExist) { + Panic(err, fmt.Sprintf("failed to remove \"%s\"", staticPath), "main") + } + + CreateDirectory(staticPath) + + CopyDeps([]string{"jquery.min.js"}, jquerySourcePath, jqueryStaticPath) + + CopyDeps([]string{"semantic.min.js", "semantic.min.css"}, fomanticSourcePath, fomanticStaticPath) + CopyDeps([]string{regExpEveryMinFile}, filepath.Join(fomanticSourcePath, "components"), filepath.Join(fomanticStaticPath, "components")) + CopyDeps([]string{regExpEveryWoff2File}, filepath.Join(fomanticSourcePath, "themes/default/assets/fonts"), filepath.Join(fomanticStaticPath, "themes/default/assets/fonts")) + + CopyDeps([]string{"lodash.min.js"}, lodashSourcePath, lodashStaticPath) +} diff --git a/scripts/syncenv/syncenv.go b/scripts/syncenv/syncenv.go new file mode 100644 index 0000000..66f429b --- /dev/null +++ b/scripts/syncenv/syncenv.go @@ -0,0 +1,215 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + "path/filepath" + "regexp" + "strings" +) + +const cli = "syncenv" + +var envFilesDir string +var envFileModel string + +var envFileBackupRegex = regexp.MustCompile(`\.env\..*\.tmp`) + +func RestoreEnvFilesBackup() { + files, err := os.ReadDir(envFilesDir) + + if err != nil { + panic(err) + } + + for _, file := range files { + if envFileBackupRegex.Match([]byte(file.Name())) { + renamed, _ := strings.CutSuffix(file.Name(), ".tmp") + os.Rename(filepath.Join(envFilesDir, file.Name()), filepath.Join(envFilesDir, renamed)) + } + } +} + +func Panic(who error, where string) { + log.Println("Sync .env* failed!") + + log.Println("Restoring .env* from backups") + RestoreEnvFilesBackup() + + log.Println("Please, check 'Error' or undo changes with: make syncenv-reset") + + log.Panicf("Error: (%s): [%s]", where, who) +} + +func usage() { + fmt.Printf("Usage: %s [OPTIONS]\n", cli) + fmt.Printf("\nE.g.: %s -dir . -model .env.example\n\n", cli) + flag.PrintDefaults() +} + +func BackupEnvFiles() { + files, err := os.ReadDir(envFilesDir) + + if err != nil { + Panic(err, "BackupEnvFiles") + } + + for _, file := range files { + if strings.Contains(file.Name(), ".env") { + data, err := os.ReadFile(file.Name()) + + if err != nil { + Panic(err, "BackupEnvFiles") + } + + err = os.WriteFile(file.Name()+".tmp", data, 0644) + + if err != nil { + Panic(err, "BackupEnvFiles") + } + } + } +} + +func RemoveEnvFilesBackup() { + files, err := os.ReadDir(envFilesDir) + + if err != nil { + Panic(err, "RemoveEnvFilesBackup") + } + + for _, file := range files { + if envFileBackupRegex.Match([]byte(file.Name())) { + err = os.Remove(file.Name()) + + if err != nil { + Panic(err, "RemoveEnvFilesBackup") + } + } + } +} + +func GetEnvFiles() (envFiles []string) { + files, err := os.ReadDir(envFilesDir) + + if err != nil { + Panic(err, "GetEnvFiles") + } + + for _, file := range files { + if strings.Contains(file.Name(), ".env") && file.Name() != envFileModel { + envFiles = append(envFiles, file.Name()) + } + } + + return +} + +func GetEnvFileModelVars() []string { + dataBytes, err := os.ReadFile(envFileModel) + + if err != nil { + Panic(err, "GetEnvFileModelVars") + } + + enVars := strings.Split(string(dataBytes), "\n") + + for i, enVar := range enVars { + enVars[i] = strings.Split(enVar, "=")[0] + } + + return enVars +} + +func SyncEnv(envModelVars []string, envFile string) { + data, err := os.ReadFile(envFile) + + if err != nil { + Panic(err, "SyncEnv") + } + + envFileVars := strings.Split(string(data), "\n") + + envFileVarsCleaned := []string{} + + for _, envFileVar := range envFileVars { + if envFileVar == "" { + continue + } + + envFileVarsCleaned = append(envFileVarsCleaned, envFileVar) + } + + envFileUpdatedVars := "" + + updatedVar := false + + for i, envModelVar := range envModelVars { + updatedVar = false + + if i+1 == len(envModelVars) { + break + } + + if envModelVar == "" { + envFileUpdatedVars += "\n" + continue + } + + for _, envFileVar := range envFileVarsCleaned { + values := strings.SplitN(envFileVar, "=", 2) + enVarName := values[0] + enVarValue := values[1] + + if envModelVar == enVarName { + envFileUpdatedVars += envModelVar + "=" + enVarValue + "\n" + updatedVar = true + break + } + } + + if !updatedVar { + envFileUpdatedVars += envModelVar + "=" + "\n" + } + } + + file, err := os.Create(envFile) + + if err != nil { + Panic(err, "SyncEnv") + } + + _, err = file.WriteString(envFileUpdatedVars) + + if err != nil { + Panic(err, "SyncEnv") + } +} + +func main() { + flag.StringVar(&envFilesDir, "dir", ".", ".env files directory") + flag.StringVar(&envFileModel, "model", ".env.example", ".env file model") + + flag.Usage = usage + + flag.Parse() + + log.Println("Creating .env* backups") + BackupEnvFiles() + + log.Println("Searching .env*") + envFiles := GetEnvFiles() + envFileModelVars := GetEnvFileModelVars() + + log.Println("Syncing .env*") + for _, envFile := range envFiles { + SyncEnv(envFileModelVars, envFile) + } + + log.Println("Removing .env* backups") + RemoveEnvFilesBackup() + + log.Println("Sync .env* completed!") +} diff --git a/scripts/upgrade/upgrade.go b/scripts/upgrade/upgrade.go new file mode 100644 index 0000000..29ecd83 --- /dev/null +++ b/scripts/upgrade/upgrade.go @@ -0,0 +1,51 @@ +package main + +import ( + "log" + "os/exec" +) + +func Panic(who error, where string) { + log.Println("Upgrade failed!") + + log.Println("Please, check 'Error' or undo changes with: make upgrade-reset") + + log.Panicf("Error: (%s): [%s]", where, who) +} + +func Run(name string, args ...string) { + if err := exec.Command(name, args...).Run(); err != nil { + Panic(err, "Run") + } +} + +func Lint() { + log.Println("Linting...") + Run("make", "lint") +} + +func main() { + log.Println("Starting upgrades...") + + Lint() + + log.Println("Upgrading Go") + Run("make", "upgrade-go") + + log.Println("Upgrading Node") + Run("make", "upgrade-node") + + log.Println("Upgrading Tooling") + Run("make", "install-tooling") + + Lint() + + log.Println("Testing...") + Run("make", "test-unit") + + log.Println("Committing upgrades") + Run("git", "add", ".", "--update") + Run("git", "commit", "-m", "chore(deps): upgrade") + + log.Println("Upgrade completed!") +} diff --git a/test/.gitkeep b/test/.gitkeep new file mode 100644 index 0000000..e69de29