1
0
mirror of https://github.com/hustcer/deepseek-review.git synced 2026-05-13 05:16:05 +08:00

1 Commits

Author SHA1 Message Date
hustcer
adf7e63560 chore: Update logo 2025-02-16 23:19:47 +08:00
21 changed files with 560 additions and 996 deletions

View File

@@ -25,43 +25,43 @@ jobs:
uses: hustcer/deepseek-review@develop
with:
max-length: 50000
# model: 'deepseek-v3' # Infinigence's DeepSeek V3 model
# model: 'deepseek-r1' # Infinigence's DeepSeek R1 model
# base-url: 'https://cloud.infini-ai.com/maas/v1' # Infinigence's API base URL
# model: 'deepseek-ai/DeepSeek-V3' # SiliconFlow's DeepSeek V3 model
model: 'deepseek-ai/DeepSeek-R1' # SiliconFlow's DeepSeek R1 model
base-url: 'https://api.siliconflow.cn/v1' # SiliconFlow's API base URL
# model: 'deepseek-v3' # Infinigence's DeepSeek V3 model
model: 'deepseek-r1' # Infinigence's DeepSeek R1 model
base-url: 'https://cloud.infini-ai.com/maas/v1' # Infinigence's API base URL
# model: 'deepseek-ai/DeepSeek-V3' # SiliconFlow's DeepSeek V3 model
# model: 'deepseek-ai/DeepSeek-R1' # SiliconFlow's DeepSeek R1 model
# base-url: 'https://api.siliconflow.cn/v1' # SiliconFlow's API base URL
# Store the chat token in GitHub Secrets, don't expose it in the workflow file
chat-token: ${{ secrets.CHAT_TOKEN }}
sys-prompt: >
As a senior Nushell engineer, perform comprehensive script review with focus on:
As a senior DevOps engineer, perform comprehensive review of shell scripts with focus on:
### 1. Core Requirements:
- Validate Nu 0.90+ compatibility
- Check structured data handling
- Verify pipeline efficiency
- Assess module organization
1. Core Requirements:
- Validate POSIX compatibility
- Check for proper error handling
- Verify safe variable usage
- Assess resource management
### 2. Security Analysis:
- Command injection prevention
- Data leakage prevention
- Safe external command usage
- Proper permission validation
2. Security Analysis:
- Shell injection prevention
- Safe file operations
- Proper permissions handling
- Secure command execution
### 3. Performance Optimization:
- Pipeline optimization
- Memory usage patterns
- Builtin vs external command usage
- Parallel execution opportunities
3. Performance Optimization:
- Efficient process management
- Proper use of subshells
- Stream handling best practices
- Avoidance of unnecessary forks
**Rules:**
- Target Nu 0.90+ features
- Highlight data flow vulnerabilities
- Suggest structured data optimizations
- Keep feedback Nu-specific
- Use modern shell terminology
Rules:
- Target bash/sh compatibility
- Highlight security vulnerabilities
- Suggest performance improvements
- Keep feedback actionable
- Use technical shell terminology
**Required output structure:**
Required output structure:
#### Script Analysis
- Key observations
@@ -73,18 +73,19 @@ jobs:
**Overall Quality:** Rating (1-5)
Use the following reference data:
```yaml
checklist:
- Compatibility: ["Nu version", "Cross-platform support", "Plugin dependencies"]
- Security: ["Input sanitization", "Temporary file handling", "Env exposure"]
- Reliability: ["Error propagation", "Null handling", "Type validation"]
- Performance: ["Lazy evaluation", "Batch processing", "Stream handling"]
- Compatibility: ["POSIX compliance", "Shell-specific features", "Portability"]
- Security: ["Input validation", "Safe eval usage", "Permission checks"]
- Reliability: ["Error handling", "Exit codes", "Signal trapping"]
- Performance: ["Process management", "I/O operations", "Subshell usage"]
examples:
- issue: "❗ Unfiltered external command arguments in line 15 (command injection risk)"
- issue: "⚠️ Plaintext credentials in environment variables"
- suggestion: "Replace `each { }` with `par-each` for parallel processing"
- suggestion: "Use builtin `from json` instead of jq for better performance"
- issue: "❗ Unquoted variable expansion in line 42 (shell injection risk)"
- issue: "⚠️ Missing error handling for rm operation in line 15"
- suggestion: "Replace backticks with $() for better readability and nesting"
- suggestion: "Use exec for file handling to reduce file descriptors"
response_template: |
#### Script Analysis

View File

@@ -1,7 +1,6 @@
# Description: This workflow runs tests for hustcer/deepseek-review.
# REF:
# - https://github.com/vyadh/nutest/blob/main/.github/workflows/tests.yaml
# - https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables#default-environment-variables
name: Run Tests
@@ -62,7 +61,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
use ${{ github.workspace }}/nutest/nutest
use ${{ github.workspace }}/nu/util.nu [prepare-awk]
use ${{ github.workspace }}/nu/review.nu [prepare-awk]
prepare-awk
(
nutest run-tests

1
.gitignore vendored
View File

@@ -1,6 +1,5 @@
.env
.env.dev
.env.local
review.md
config.yml
prompts.yaml

View File

@@ -1,70 +1,6 @@
# Changelog
All notable changes to this project will be documented in this file.
## [1.17.0] - 2025-04-11
### Bug Fixes
- Read default `include` and `exclude` patterns from config for local code review (#170)
### Features
All the following changes are for local code review only:
- Add code review for `git show head:path/to/file` patch command support (#171)
- Add write code review result to file by `--output` flag support (#172)
## [1.16.0] - 2025-04-05
### Documentation
- Add alias setup guide for `powershell` (#163)
### Features
- Add OpenRouter deepseek model support (#167)
### Miscellaneous Tasks
- Add alias setup guide for `fish`
- Add openrouter.ai config example
- Set minimum required `nushell` version to v0.103
### Refactor
- Refactor `get-diff` custom command (#164)
- Refactor diff handling by moving logic to separate module (#165)
- Replace custom `kv.nu` module with `std-rfc/kv` for key-value functionality (#166)
## [1.15.0] - 2025-03-23
### Features
- Add example code review prompts for frontend, java and rust to `config.example.yml` (#138)
- Post a comment to the PR to notify the user when no `CHAT_TOKEN` is provided (#143)
- Add nushell version check and notify for update (#144)
- Add `--config` option to specify config file path for local code review (#146)
- Support local DeepSeek model running on Ollama (#152)
- Add repo of current directory code review support (#161)
### Miscellaneous Tasks
- Publish test summary (#133)
- Update tests status badge to README
- Update code review prompt for current nushell repo (#139)
- Use SiliconFlow's DeepSeek API
- Some code refactor (#149)
- Add `just test` task to run tests locally
### Refactor
- Simplify `is-safe-git` common util (#150)
- Enhance the glob pattern handling in `glob-to-regex` function (#151)
### Deps
- Upgrade `Nushell` to `v0.103.0`
## [1.12.0] - 2025-02-16
### Bug Fixes

BIN
CR.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

View File

@@ -21,7 +21,6 @@ set dotenv-load := true
set positional-arguments := true
# Just commands aliases
alias t := test
alias cr := code-review
# Use `just --evaluate` to show env vars
@@ -49,10 +48,6 @@ code-review *OPTIONS:
@overlay use {{ join(DEEPSEEK_REVIEW_PATH, 'nu', 'review.nu') }}; \
deepseek-review {{OPTIONS}}
# Run the test cases locally by nutest
test:
@use $'($nu.default-config-dir)/lib/nutest' *; run-tests
# Plugins need to be registered only once after nu v0.61
_setup:
@register -e json {{ join(NU_DIR, _query_plugin) }}

View File

@@ -1,3 +1,7 @@
<p align="center">
<img src="./CR.jpg" width="90" height="60" alt="DeepSeek Code Review Logo" />
</p>
# DeepSeek Code Review
![Tests](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgist.githubusercontent.com%2Fhustcer%2Fb99391ee59016b17d0befe3331387e89%2Fraw%2Ftest-summary.json&query=%24.total&label=Tests)
@@ -22,8 +26,6 @@
- Streaming Output Support for Local Code Review
- Review Remote GitHub PRs Directly from Your Local CLI
- Review Commit Changes with DeepSeek for Any Local Repository by CLI
- Support On-demand Changes Generation via `git show`/`git diff` Command for Further Code Review
- Output Code Review Result to Specified File in Markdown Format
- Cross-platform Compatibility: Designed to function seamlessly across all platforms capable of running [Nushell](https://github.com/nushell/nushell)
### Both GH Action & Local
@@ -164,7 +166,7 @@ With this setup, DeepSeek code review will not run automatically upon PR creatio
To perform code reviews locally(should works for `macOS`, `Ubuntu`, and `Windows`), you need to install the following tools:
- [`Nushell`](https://www.nushell.sh/book/installation.html). It is recommended to install the latest versions(min version required: `0.103`).
- [`Nushell`](https://www.nushell.sh/book/installation.html). It is recommended to install the latest versions.
- The latest version of [`awk`](https://github.com/onetrueawk/awk) or [`gawk`](https://www.gnu.org/software/gawk/) is required, with `gawk` being the preferred choice.
- Clone this repository to your local machine, navigate to the repository directory, and run `nu cr -h`. You should see an output similar to the following:
@@ -176,7 +178,7 @@ Usage:
Flags:
-d, --debug: Debug mode
-r, --repo <string>: GitHub repo name, e.g. hustcer/deepseek-review
-r, --repo <string>: GitHub repo name, e.g. hustcer/deepseek-review, or local repo path / alias
-n, --pr-number <string>: GitHub PR number
-k, --gh-token <string>: Your GitHub token, fallback to GITHUB_TOKEN env var
-t, --diff-to <string>: Diff to git REF
@@ -185,14 +187,11 @@ Flags:
-l, --max-length <int>: Maximum length of the content for review, 0 means no limit.
-m, --model <string>: Model name, or read from CHAT_MODEL env var, `deepseek-chat` by default
-b, --base-url <string>: DeepSeek API base URL, fallback to BASE_URL env var
-U, --chat-url <string>: DeepSeek Model chat full API URL, e.g. http://localhost:11535/api/chat
-s, --sys-prompt <string>: Default to $DEFAULT_OPTIONS.SYS_PROMPT,
-u, --user-prompt <string>: Default to $DEFAULT_OPTIONS.USER_PROMPT,
-i, --include <string>: Comma separated file patterns to include in the code review
-x, --exclude <string>: Comma separated file patterns to exclude in the code review
-T, --temperature <float>: Temperature for the model, between `0` and `2`, default value `1.0`
-C, --config <string>: Config file path, default to `config.yml`
-o, --output <string>: Output file path
-h, --help: Display the help message for this command
Parameters:
@@ -202,77 +201,34 @@ Parameters:
### Environment Configuration
To perform code review locally, you need to modify the configuration file. The repository already provides a configuration example [`config.example.yml`](https://github.com/hustcer/deepseek-review/blob/main/config.example.yml). Copy it to `config.yml` and modify it according to your actual needs. **Read the comments in the configuration file carefully**, as they explain the purpose of each configuration item.
To perform code reviews locally, you need to modify the configuration file. A sample configuration file [`config.example.yml`](https://github.com/hustcer/deepseek-review/blob/main/config.example.yml) is already provided in the repository. Copy it to `config.yml` and adjust it according to your actual setup.
> [!WARNING]
>
> The `config.yml` configuration file is **only used locally** and will not be utilized in GitHub Workflow. **Sensitive information** in this file should be properly secured and **never committed** to the code repository.
>
> The `config.yml` configuration file is only used locally and will not be utilized in GitHub
> Workflow. Please securely store any sensitive information in it and avoid committing it
> to the code repository.
**Create Command Alias**
For convenience in performing code review across any local repository, create a command alias. For example:
```sh
# For Nushell: Modify config.nu and add:
alias cr = nu /absolute/path/to/deepseek-review/cr --config /absolute/path/to/deepseek-review/config.yml
# Modify ~/.zshrc for zsh or ~/.bashrc for bash or ~/.config/fish/config.fish for fish and add:
alias cr="nu /absolute/path/to/deepseek-review/cr --config /absolute/path/to/deepseek-review/config.yml"
# After sourcing the modified profile, use `cr` for code review
# For Windows powershell users please set cr alias by editing $PROFILE and add:
function cr {
nu D:\absolute\path\to\deepseek-review\cr --config D:\absolute\path\to\deepseek-review\config.yml @args
}
# Then restart the terminal or run `. $PROFILE` in pwsh to make `cr` work
```
### Review Local Repository
To review a local repository:
- Navigate to the Git repository directory.
- Use the `cr` command to review current modifications, provided that `config.yml` is correctly configured.
**Usage Examples**
```sh
# Perform code review on the `git diff` changes in current directory
cr
# Perform code review on the `git diff f536acc` changes in current directory
cr --diff-from f536acc
# Perform code review on the `git diff f536acc` changes and output result to review.md
cr --diff-from f536acc --output review.md
# Perform code review on the `git diff f536acc 0dd0eb5` changes in current directory
cr --diff-from f536acc --diff-to 0dd0eb5
# Review the changes in current directory using the `--patch-cmd` flag
cr --patch-cmd 'git diff head~3'
cr -c 'git show head~3'
cr -c 'git diff 2393375 71f5a31'
cr -c 'git diff 2393375 71f5a31 nu/*'
cr -c 'git diff 2393375 71f5a31 :!nu/*'
# Dangerous commands like `cr -c 'git show head~3; rm ./*'` will not be allowed
```
### Review Remote GitHub PR Locally
When reviewing a remote GitHub PR locally:
- Always specify the PR number via `--pr-number`
- Use `--repo` to indicate the target repository (e.g., `hustcer/deepseek-review`). If `--repo` is omitted, the tool reads `settings.default-github-repo` from `config.yml`.
**Usage Examples**
### Usage Examples
```sh
# Perform code review on the `git diff` changes in the local DEFAULT_LOCAL_REPO repo
nu cr
# Perform code review on the `git diff f536acc` changes in the local DEFAULT_LOCAL_REPO repo
nu cr --diff-from f536acc
# Perform code review on the `git diff f536acc 0dd0eb5` changes in the local DEFAULT_LOCAL_REPO repo
nu cr --diff-from f536acc --diff-to 0dd0eb5
# Review the changes in the local `DEFAULT_LOCAL_REPO` repo using the `--patch-cmd` flag
nu cr --patch-cmd 'git diff head~3'
nu cr -c 'git show head~3'
nu cr -c 'git diff 2393375 71f5a31'
nu cr -c 'git diff 2393375 71f5a31 nu/*'
nu cr -c 'git diff 2393375 71f5a31 :!nu/*'
# Dangerous commands like `nu cr -c 'git show head~3; rm ./*'` will not be allowed
# Perform code review on PR #31 in the remote DEFAULT_GITHUB_REPO repo
cr --pr-number 31
nu cr --pr-number 31
# Perform code review on PR #31 in the remote hustcer/deepseek-review repo
cr --pr-number 31 --repo hustcer/deepseek-review
# Perform code review on PR #31 and exclude changes of pnpm-lock.yaml
cr --pr-number 31 --exclude pnpm-lock.yaml
nu cr --pr-number 31 --repo hustcer/deepseek-review
```
## License

View File

@@ -19,9 +19,7 @@
- 本地代码审查的时候支持流式输出
- 通过本地 CLI 直接审查远程 GitHub PR
- 通过本地 CLI 使用 DeepSeek 审查任何本地仓库的提交变更
- 允许通过自定义 `git show`/`git diff` 命令生成变更记录并进行审查
- 允许将代码审查结果以 Markdown 格式输出到指定文件
- 通过本地 CLI 使用 DeepSeek 分析任何本地仓库的提交变更
- 跨平台:理论上只要能运行 [Nushell](https://github.com/nushell/nushell) 即可使用本工具
### 本地或 GH Action
@@ -161,7 +159,7 @@ DeepSeek 接口调用入参:
在本地进行代码审查,支持 `macOS`, `Ubuntu` & `Windows` 不过需要安装以下工具:
- [`Nushell`](https://www.nushell.sh/book/installation.html), 建议安装最新版本(最低版本 `0.103`)
- [`Nushell`](https://www.nushell.sh/book/installation.html), 建议安装最新版本
- [`awk`](https://github.com/onetrueawk/awk) 或者 [`gawk`](https://www.gnu.org/software/gawk/) 的最新版版本,优先推荐 `gawk`
- 接下来只需要把本仓库代码克隆到本地,然后进入仓库目录执行 `nu cr -h` 即可看到类似如下输出:
@@ -173,7 +171,7 @@ Usage:
Flags:
-d, --debug: Debug mode
-r, --repo <string>: GitHub repo name, e.g. hustcer/deepseek-review
-r, --repo <string>: GitHub repo name, e.g. hustcer/deepseek-review, or local repo path / alias
-n, --pr-number <string>: GitHub PR number
-k, --gh-token <string>: Your GitHub token, fallback to GITHUB_TOKEN env var
-t, --diff-to <string>: Diff to git REF
@@ -182,14 +180,11 @@ Flags:
-l, --max-length <int>: Maximum length of the content for review, 0 means no limit.
-m, --model <string>: Model name, or read from CHAT_MODEL env var, `deepseek-chat` by default
-b, --base-url <string>: DeepSeek API base URL, fallback to BASE_URL env var
-U, --chat-url <string>: DeepSeek Model chat full API URL, e.g. http://localhost:11535/api/chat
-s, --sys-prompt <string>: Default to $DEFAULT_OPTIONS.SYS_PROMPT,
-u, --user-prompt <string>: Default to $DEFAULT_OPTIONS.USER_PROMPT,
-i, --include <string>: Comma separated file patterns to include in the code review
-x, --exclude <string>: Comma separated file patterns to exclude in the code review
-T, --temperature <float>: Temperature for the model, between `0` and `2`, default value `1.0`
-C, --config <string>: Config file path, default to `config.yml`
-o, --output <string>: Output file path
-h, --help: Display the help message for this command
Parameters:
@@ -199,73 +194,33 @@ Parameters:
### 环境配置
在本地进行代码审查需要先修改配置文件,仓库里已经有了 [`config.example.yml`](https://github.com/hustcer/deepseek-review/blob/main/config.example.yml) 配置文件示例,将其拷贝到 `config.yml` 然后根据自己的实际情况进行修改即可,在修改配置文件的过程中请仔细阅读其中的注释,注释会说明每个配置项的作用
在本地进行代码审查需要先修改配置文件,仓库里已经有了 [`config.example.yml`](https://github.com/hustcer/deepseek-review/blob/main/config.example.yml) 配置文件示例,将其拷贝到 `config.yml` 然后根据自己的实际情况进行修改即可。
> [!WARNING]
>
> `config.yml` 配置文件仅在本地使用,在 GitHub Workflow 里面不会使用,里面的敏感信息请
> 妥善保存,不要提交到代码仓库里面
>
**创建命令别名**
为了方便您可以在任意本地仓库进行代码审查需要创建一个别名,比如:
```sh
# Nushell: 修改其 config.nu 配置文件,添加:
alias cr = nu /absolute/path/to/deepseek-review/cr --config /absolute/path/to/deepseek-review/config.yml
# Modify ~/.zshrc for zsh or ~/.bashrc for bash or ~/.config/fish/config.fish for fish and add:
alias cr="nu /absolute/path/to/deepseek-review/cr --config /absolute/path/to/deepseek-review/config.yml"
# After sourcing the profile you have edit, you can use `cr` now
# For Windows powershell users please set cr alias by editing $PROFILE and add:
function cr {
nu D:\absolute\path\to\deepseek-review\cr --config D:\absolute\path\to\deepseek-review\config.yml @args
}
# Then restart the terminal or run `. $PROFILE` in pwsh to make `cr` work
```
之后就可以通过 `cr` 命令来进行代码审查了。
### 审查本地仓库
对本地仓库进行代码审查时需要先切换到 Git 仓库所在目录,然后通过 `cr` 命令即可对当前目录的当前修改进行代码审查,前提是您已经对 `config.yml` 进行了正确的配置。
**使用举例**
```sh
# 对本地当前目录所在仓库 `git diff` 修改内容进行代码审查
cr
# 对本地当前目录所在仓库 `git diff f536acc` 修改内容进行代码审查
cr --diff-from f536acc
# 对本地当前目录所在仓库 `git diff f536acc` 修改内容进行代码审查并将审查结果输出到 review.md
cr --diff-from f536acc --output review.md
# 对本地当前目录所在仓库 `git diff f536acc 0dd0eb5` 修改内容进行代码审查
cr --diff-from f536acc --diff-to 0dd0eb5
# 通过 --patch-cmd 参数对本地当前目录所在仓库变更内容进行审查
cr --patch-cmd 'git diff head~3'
cr -c 'git show head~3'
cr -c 'git diff 2393375 71f5a31'
cr -c 'git diff 2393375 71f5a31 nu/*'
cr -c 'git diff 2393375 71f5a31 :!nu/*'
# 像 `cr -c 'git show head~3; rm ./*'` 这样危险的命令将会被禁止
```
### 本地审查远程 GitHub PR
在本地对远程 GitHub 仓库的 PR 进行审查的时候一定要通过 `--pr-number` 传入待审查的 PR 编号,以及 `--repo` 指明待审查的仓库,比如 `hustcer/deepseek-review`, 如果没有指定 `--repo` 参数则从 config.yml 里面的 `settings.default-github-repo` 配置项读取待审查的仓库。
**使用举例**
### 使用举例
```sh
# 对本地 DEFAULT_LOCAL_REPO 仓库 `git diff` 修改内容进行代码审查
nu cr
# 对本地 DEFAULT_LOCAL_REPO 仓库 `git diff f536acc` 修改内容进行代码审查
nu cr --diff-from f536acc
# 对本地 DEFAULT_LOCAL_REPO 仓库 `git diff f536acc 0dd0eb5` 修改内容进行代码审查
nu cr --diff-from f536acc --diff-to 0dd0eb5
# 通过 --patch-cmd 参数对本地 DEFAULT_LOCAL_REPO 仓库变更内容进行审查
nu cr --patch-cmd 'git diff head~3'
nu cr -c 'git show head~3'
nu cr -c 'git diff 2393375 71f5a31'
nu cr -c 'git diff 2393375 71f5a31 nu/*'
nu cr -c 'git diff 2393375 71f5a31 :!nu/*'
# 像 `nu cr -c 'git show head~3; rm ./*'` 这样危险的命令将会被禁止
# 对远程 DEFAULT_GITHUB_REPO 仓库编号为 31 的 PR 进行代码审查
cr --pr-number 31
nu cr --pr-number 31
# 对远程 hustcer/deepseek-review 仓库编号为 31 的 PR 进行代码审查
cr --pr-number 31 --repo hustcer/deepseek-review
# 对 PR 进行审查的时候排除 pnpm-lock.yaml 文件的变更
cr --pr-number 31 --exclude pnpm-lock.yaml
nu cr --pr-number 31 --repo hustcer/deepseek-review
```
## 许可

View File

@@ -60,7 +60,7 @@ runs:
- name: Setup Nu
uses: hustcer/setup-nu@v3
with:
version: 0.103.0
version: 0.102.0
- name: DeepSeek Code Review
shell: nu {0}

View File

@@ -27,6 +27,8 @@ settings:
# This token is used to fetch the PR changes from GitHub API
# Default value will be ${{ github.token }} if used in GitHub Actions
github-token: 'YOUR_GITHUB_TOKEN'
# Default local repository to review, could be overrode by '-r' or '--repo'
default-local-repo: 'review'
# Default GitHub repository to review, could be overrode by '-r' or '--repo' if used with `-n` or `--pr-number`
default-github-repo: 'hustcer/deepseek-review'
# Include changes in the following file patterns
@@ -37,15 +39,6 @@ settings:
# Multiple providers could be defined, select the one by name in 'settings.provider'
# This way you could switch between different predefined providers easily
providers:
- name: ollama-local
token: 'empty'
chat-url: http://localhost:11555/api/chat
models:
- name: deepseek-r1
alias: r1
enabled: true
description: 'DeepSeek R1 model running on Ollama'
- name: 'DeepSeek'
token: 'YOUR_DEEPSEEK_TOKEN' # Required, The API token for the provider
base-url: 'https://api.deepseek.com'
@@ -71,17 +64,16 @@ providers:
alias: r1
description: 'SiliconFlow DeepSeek R1 model'
- name: OpenRouter
token: sk-or-v1-*****
base-url: https://openrouter.ai/api/v1
models:
- name: deepseek/deepseek-chat-v3-0324:free
alias: v3
enabled: true
description: 'OpenRouter DeepSeek V3 model'
- name: deepseek/deepseek-r1:free
alias: r1
description: 'OpenRouter DeepSeek R1 model'
# Multiple local repositories could be defined, select the one by name in 'settings.default-local-repo'
# You can also use `-r` or `--repo` to specify the local repository to review by name to override the default
local-repos:
- name: 'review'
path: '/Users/hustcer/deepseek-review'
- name: 'milestone'
path: '/Users/hustcer/milestone-action'
- name: 'setup-nu'
path: '/Users/hustcer/setup-nu'
# Multiple Prompts could be defined, select the one by name in 'settings.user-prompt' or 'settings.system-prompt'
prompts:
@@ -95,219 +87,6 @@ prompts:
Identify potential issues such as code style violations, logical errors, security vulnerabilities, and provide
improvement suggestions. Clearly list the problems and recommendations in a concise manner.
- name: frontend
prompt: >
As a senior Frontend engineer, perform comprehensive code review with focus on:
### 1. Core Requirements:
- Validate ES specification compliance
- Check component design patterns
- Verify state management efficiency
- Assess accessibility implementation
### 2. Security Analysis:
- XSS prevention measures
- CSRF protection implementation
- Safe third-party dependency usage
- Sensitive data handling practices
### 3. Performance Optimization:
- Resource loading strategies
- Rendering performance optimization
- Memory leak prevention
- Bundle size management
**Rules:**
- Target React/Vue framework conventions
- Highlight security vulnerabilities
- Suggest performance metrics improvements
- Provide code snippets for fixes
- Use modern frontend terminology
**Required output structure:**
#### Code Analysis
- Key observations
#### Security Review
- Vulnerability findings
#### Optimization Suggestions
- Performance improvements
**Overall Quality:** Rating (1-5)
```yaml
checklist:
- Compatibility: ["Browser support", "Responsive design", "ES specification"]
- Security: ["Input sanitization", "CORS configuration", "Cookie flags"]
- Reliability: ["Error boundaries", "Loading states", "Fallback UI"]
- Performance: ["Lazy loading", "Code splitting", "Rendering optimization"]
examples:
- issue: "❗ Unsanitized HTML insertion in line 28 (XSS risk)"
- issue: "⚠️ Missing `rel='noreferrer'` on external links in line 15"
- suggestion: "Implement virtual scrolling for large datasets in TableComponent"
- suggestion: "Convert CSS-in-JS to CSS Modules for better tree-shaking"
response_template: |
#### Code Analysis
- {{observations}}
{{#security_issues}}
#### Security Review
- {{security_issues}}
{{/security_issues}}
{{#optimizations}}
#### Optimization Suggestions
- {{optimizations}}
{{/optimizations}}
**Overall Quality:** {{rating}}
```
- name: java
prompt: >
As a senior Java Backend engineer, perform comprehensive code review with focus on:
### 1. Core Requirements:
- Validate Java coding standards compliance
- Check enterprise design patterns implementation
- Verify resource management efficiency
- Assess concurrency control mechanisms
### 2. Security Analysis:
- SQL/NoSQL injection prevention
- Sensitive data encryption handling
- Proper authentication/authorization
- Secure session management
### 3. Performance Optimization:
- Thread pool configuration analysis
- Database connection management
- Cache strategy evaluation
- GC tuning opportunities
**Rules:**
- Target Spring Boot/Jakarta EE frameworks
- Highlight OWASP TOP 10 vulnerabilities
- Suggest JVM optimization strategies
- Provide code snippets for fixes
- Use enterprise Java terminology
**Required output structure:**
#### Code Analysis
- Key observations
#### Security Review
- Vulnerability findings
#### Optimization Suggestions
- Performance improvements
**Overall Quality:** Rating (1-5)
```yaml
checklist:
- Compatibility: ["JDK version", "Framework version", "API contracts"]
- Security: ["Input validation", "CSRF protection", "Security headers"]
- Reliability: ["Exception handling", "Transaction management", "Circuit breakers"]
- Performance: ["Connection pooling", "Query optimization", "Object reuse"]
examples:
- issue: "❗ Unvalidated user input in DAO layer (SQL injection risk)[2,3](@ref)"
- issue: "⚠️ Plaintext credentials storage in properties file[2](@ref)"
- suggestion: "Replace synchronized blocks with ReentrantLock for better concurrency[3](@ref)"
- suggestion: "Implement prepared statement caching in JDBC configuration[3](@ref)"
response_template: |
#### Code Analysis
- {{observations}}
{{#security_issues}}
#### Security Review
- {{security_issues}}
{{/security_issues}}
{{#optimizations}}
#### Optimization Suggestions
- {{optimizations}}
{{/optimizations}}
**Overall Quality:** {{rating}}
```
- name: rust
prompt: >
As a senior Rust engineer, perform comprehensive code review with focus on:
### 1. Core Requirements:
- Validate Rust coding standards compliance
- Check memory safety guarantees
- Verify concurrency model correctness
- Assess error handling patterns
### 2. Security Analysis:
- Unsafe code usage validation
- Data race prevention measures
- Input sanitization practices
- Supply chain security checks
### 3. Performance Optimization:
- Memory allocation patterns
- Iterator vs loop efficiency
- Parallel execution opportunities
- Zero-cost abstraction utilization
**Rules:**
- Target Rust 2021 edition features
- Highlight memory safety violations
- Suggest lifetime optimization strategies
- Provide unsafe code alternatives
- Use Rust ecosystem terminology
**Required output structure:**
#### Code Analysis
- Key observations
#### Security Review
- Vulnerability findings
#### Optimization Suggestions
- Performance improvements
**Overall Quality:** Rating (1-5)
```yaml
checklist:
- Compatibility: ["Edition compliance", "Crate versioning", "FFI safety"]
- Security: ["Unsafe scoping", "Panic safety", "Trait bounds"]
- Reliability: ["Error propagation", "Test coverage", "Documentation"]
- Performance: ["Allocation tracking", "Concurrency patterns", "SIMD usage"]
examples:
- issue: "❗ Unmarked unsafe block in line 75 (memory safety violation risk)[1](@ref)"
- issue: "⚠️ Missing error handling for Result in line 42[1](@ref)"
- suggestion: "Replace Box<dyn Trait> with impl Trait for better monomorphization[1](@ref)"
- suggestion: "Use crossbeam-channel instead of std::sync::mpsc for improved throughput[3](@ref)"
response_template: |
#### Code Analysis
- {{observations}}
{{#security_issues}}
#### Security Review
- {{security_issues}}
{{/security_issues}}
{{#optimizations}}
#### Optimization Suggestions
- {{optimizations}}
{{/optimizations}}
**Overall Quality:** {{rating}}
```
- name: strict-dev
prompt: >
Act as a senior engineer performing rigorous code review. Analyze the provided git diff output through

20
cr
View File

@@ -4,14 +4,14 @@
# Description: A wrapper for nu/review.nu as the main entry point of the project.
use nu/config.nu *
use nu/common.nu [hr-line, ECODE]
use nu/review.nu [deepseek-review]
use nu/common.nu [hr-line, check-nushell, ECODE]
# Use DeepSeek AI to review code changes locally or in GitHub Actions
def main [
token?: string, # Your DeepSeek API token, fallback to CHAT_TOKEN env var
--debug(-d), # Debug mode
--repo(-r): string, # GitHub repo name, e.g. hustcer/deepseek-review
--repo(-r): string, # GitHub repo name, e.g. hustcer/deepseek-review, or local repo path / alias
--pr-number(-n): string, # GitHub PR number
--gh-token(-k): string, # Your GitHub token, fallback to GITHUB_TOKEN env var
--diff-to(-t): string, # Diff to git REF
@@ -20,27 +20,23 @@ def main [
--max-length(-l): int, # Maximum length of the content for review, 0 means no limit.
--model(-m): string, # Model name, or read from CHAT_MODEL env var, `deepseek-chat` by default
--base-url(-b): string, # DeepSeek API base URL, fallback to BASE_URL env var
--chat-url(-U): string, # DeepSeek Model chat full API URL, e.g. http://localhost:11535/api/chat
--sys-prompt(-s): string # Default to $DEFAULT_OPTIONS.SYS_PROMPT,
--user-prompt(-u): string # Default to $DEFAULT_OPTIONS.USER_PROMPT,
--include(-i): string, # Comma separated file patterns to include in the code review
--exclude(-x): string, # Comma separated file patterns to exclude in the code review
--temperature(-T): float, # Temperature for the model, between `0` and `2`, default value `1.0`
--config(-C): string # Config file path, default to `config.yml`
--output(-o): string, # Output file path
--temperature(-T): float, # Temperature for the model, between `0` and `2`, default value `1.0`, Only for V3
] {
check-nushell
config-check --config=$config
config-load --debug=$debug --config=$config --model=$model
config-check
config-load --debug=$debug --repo=$repo --model=$model
(
deepseek-review $token
--repo=$repo
--debug=$debug
--output=$output
--include=$include
--exclude=$exclude
--model=$env.CHAT_MODEL
--base-url=$base_url
--chat-url=$chat_url
--gh-token=$gh_token
--diff-to=$diff_to
--diff-from=$diff_from
@@ -50,7 +46,5 @@ def main [
--sys-prompt=$sys_prompt
--user-prompt=$user_prompt
--temperature=$temperature
--include=($include | default $env.INCLUDE_PATTERNS?)
--exclude=($exclude | default $env.EXCLUDE_PATTERNS?)
)
}

View File

@@ -8,15 +8,12 @@ words:
- MPFR
- nuon
- pwsh
- JDBC
- mpsc
- nuons
- ECODE
- vyadh
- nutest
- endfor
- dotenv
- Ollama
- hustcer
- Nushell
- creatio
@@ -26,13 +23,7 @@ words:
- linewise
- Subshell
- subshells
- noreferrer
- OPENROUTER
- Infinigence
- SILICONFLOW
- USERPROFILE
- Unsanitized
- Unvalidated
- monomorphization
ignorePaths:
- config.yml

View File

@@ -1,7 +1,7 @@
{
"name": "deepseek-review",
"version": "1.17.0",
"actionVer": "v1.17",
"version": "1.12.0",
"actionVer": "v1.12",
"author": "hustcer",
"license": "MIT",
"github": "https://github.com/hustcer/deepseek-review",

View File

@@ -4,8 +4,6 @@
# Description: Common helpers for DeepSeek-Review
#
use std-rfc/kv ['kv set', 'kv get']
# Commonly used exit codes
export const ECODE = {
SUCCESS: 0,
@@ -18,8 +16,6 @@ export const ECODE = {
CONDITION_NOT_SATISFIED: 8,
}
export const GITHUB_API_BASE = 'https://api.github.com'
# If current host is Windows
export def windows? [] {
# Windows / Darwin
@@ -59,27 +55,6 @@ export def compare-ver [v1: string, v2: string] {
0
}
# Check nushell version and notify user to upgrade it if outdated
# Check version once daily and cache the result
export def check-nushell [--debug] {
const _DATE_FMT = '%Y.%m.%d'
const V_KEY = 'NU-VERSION-CHECK@DEEPSEEK-REVIEW'
let version_cached = kv get -u $V_KEY
let today = date now | format date $_DATE_FMT
let check = if ($version_cached | is-empty) or $version_cached.date? != $today {
let $check = try { ({ ...(version check), date: $today }) } catch { ({ current: true }) }
if $debug { print 'Checking for the latest Nushell version...'; $check | print }
kv set -u $V_KEY $check --return value
} else {
$version_cached
}
# If the current version is the latest after user upgrade, return
if $check.current or (compare-ver $check.latest (version).version) == 0 { return }
print $'(char nl) (ansi yr) WARNING: (ansi reset) Your Nushell is (ansi r)OUTDATED(ansi reset)'
print $' ------------> Please upgrade Nushell to the latest version: (ansi g)($check.latest)(ansi reset) <------------'
print -n (char nl)
}
# Converts a .env file into a record
# may be used like this: open .env | load-env
# works with quoted and unquoted .env files
@@ -168,9 +143,3 @@ export def has-ref [
let parse = (do -i { git rev-parse --verify -q $ref } | complete)
if ($parse.stdout | is-empty) { false } else { true }
}
# Notify the user that the `CHAT_TOKEN` hasn't been configured
export const NO_TOKEN_TIP = (
"**Notice:** It looks like you're using [`hustcer/deepseek-review`](https://github.com/hustcer/deepseek-review), but the `CHAT_TOKEN` hasn't " +
"been configured in your repo's **Variables/Secrets**. Please ensure this token is set for proper functionality. For step-by-step guidance, refer " +
"to the **CHAT_TOKEN Config** section of [README](https://github.com/hustcer/deepseek-review/blob/main/README.md#code-review-with-github-action).")

View File

@@ -91,9 +91,9 @@ def check-models [options: record] {
}
# Check if the config.yml file exists and if it's valid
export def config-check [--config: string = $SETTING_FILE] {
file-exists $config
let options = open $config
export def config-check [] {
file-exists $SETTING_FILE
let options = open $SETTING_FILE
check-prompts $options
check-providers $options
check-models $options
@@ -117,17 +117,22 @@ def get-model-envs [settings: record, model?: string = ''] {
| get -i 0.name
| default $model
{ CHAT_TOKEN: $provider.token?, BASE_URL: $provider.base-url?, CHAT_URL: $provider.chat-url?, CHAT_MODEL: $model_name }
{ CHAT_TOKEN: $provider.token?, BASE_URL: $provider.base-url?, CHAT_MODEL: $model_name }
}
# Load the config.yml file to the environment
export def --env config-load [
--debug(-d), # Print the loaded environment variables
--config(-C): string, # The config file path, default to `config.yml`
--repo(-r): string, # Load the specified local repository by name
--model(-m): string, # Load the specified model by name
] {
let all_settings = open ($config | default $SETTING_FILE)
let all_settings = open $SETTING_FILE
let settings = $all_settings | get settings? | default {}
let local_repo = $all_settings.local-repos
| default []
| where name == ($repo | default $settings.default-local-repo? | default '')
| get -i 0.path
| default $repo
let user_prompt = $all_settings.prompts?.user?
| default []
@@ -150,6 +155,7 @@ export def --env config-load [
GITHUB_TOKEN: $settings.github-token,
EXCLUDE_PATTERNS: $settings.exclude-patterns,
INCLUDE_PATTERNS: $settings.include-patterns,
DEFAULT_LOCAL_REPO: $local_repo,
DEFAULT_GITHUB_REPO: $settings.default-github-repo,
}
load-env $env_vars

View File

@@ -1,150 +0,0 @@
#!/usr/bin/env nu
# Author: hustcer
# Created: 2025/04/02 20:02:15
# Description: Diff command for DeepSeek-Review
use common.nu [GITHUB_API_BASE, ECODE, git-check, has-ref]
use util.nu [generate-include-regex, generate-exclude-regex, prepare-awk, is-safe-git]
# If the PR title or body contains any of these keywords, skip the review
const IGNORE_REVIEW_KEYWORDS = ['skip review' 'skip cr']
# Get the diff content from GitHub PR or local git changes and apply filters
export def get-diff [
--repo: string, # GitHub repository name
--pr-number: string, # GitHub PR number
--diff-to: string, # Diff to git ref
--diff-from: string, # Diff from git ref
--include: string, # Comma separated file patterns to include in the code review
--exclude: string, # Comma separated file patterns to exclude in the code review
--patch-cmd: string, # The `git show` or `git diff` command to get the diff content
] {
let content = (
get-diff-content --repo $repo --pr-number $pr_number --patch-cmd $patch_cmd
--diff-to $diff_to --diff-from $diff_from --include $include --exclude $exclude)
if ($content | is-empty) {
print $'(ansi g)Nothing to review.(ansi reset)'
exit $ECODE.SUCCESS
}
apply-file-filters $content --include $include --exclude $exclude
}
# Get diff content from GitHub PR or local git changes
def get-diff-content [
--repo: string, # GitHub repository name
--pr-number: string, # GitHub PR number
--diff-to: string, # Diff to git ref
--diff-from: string, # Diff from git ref
--include: string, # Comma separated file patterns to include in the code review
--exclude: string, # Comma separated file patterns to exclude in the code review
--patch-cmd: string, # The `git show` or `git diff` command to get the diff content
] {
let local_repo = $env.PWD
if ($pr_number | is-not-empty) {
get-pr-diff --repo $repo $pr_number
} else if ($diff_from | is-not-empty) {
get-ref-diff $diff_from --diff-to $diff_to
} else if not (git-check $local_repo --check-repo=1) {
print $'Current directory ($local_repo) is (ansi r)NOT(ansi reset) a git repo, bye...(char nl)'
exit $ECODE.CONDITION_NOT_SATISFIED
} else if ($patch_cmd | is-not-empty) {
get-patch-diff $patch_cmd
} else {
git diff
}
}
# Get the diff content of the specified GitHub PR,
# if the PR description contains the skip keyword, exit
def get-pr-diff [
--repo: string, # GitHub repository name
pr_number: string, # GitHub PR number
] {
let BASE_HEADER = [Authorization $'Bearer ($env.GH_TOKEN)' Accept application/vnd.github.v3+json]
let DIFF_HEADER = [Authorization $'Bearer ($env.GH_TOKEN)' Accept application/vnd.github.v3.diff]
if ($repo | is-empty) {
print $'(ansi r)Please provide the GitHub repository name by `--repo` option.(ansi reset)'
exit $ECODE.INVALID_PARAMETER
}
let description = http get -H $BASE_HEADER $'($GITHUB_API_BASE)/repos/($repo)/pulls/($pr_number)'
| select title body | values | str join "\n"
# Check if the PR title or body contains keywords to skip the review
if ($IGNORE_REVIEW_KEYWORDS | any {|it| $description =~ $it }) {
print $'(ansi r)The PR title or body contains keywords to skip the review, bye...(ansi reset)'
exit $ECODE.SUCCESS
}
# Get the diff content of the PR
http get -H $DIFF_HEADER $'($GITHUB_API_BASE)/repos/($repo)/pulls/($pr_number)' | str trim
}
# Get diff content from local git changes
def get-ref-diff [
diff_from: string, # Diff from git REF
--diff-to: string, # Diff to git ref
] {
# Validate the git refs
if not (has-ref $diff_from) {
print $'(ansi r)The specified git ref ($diff_from) does not exist, please check it again.(ansi reset)'
exit $ECODE.INVALID_PARAMETER
}
if ($diff_to | is-not-empty) and not (has-ref $diff_to) {
print $'(ansi r)The specified git ref ($diff_to) does not exist, please check it again.(ansi reset)'
exit $ECODE.INVALID_PARAMETER
}
git diff $diff_from ($diff_to | default HEAD)
}
# Get the diff content from the specified git command
def get-patch-diff [
cmd: string # The `git show` or `git diff` command to get the diff content
] {
let valid = is-safe-git $cmd
if not $valid {
exit $ECODE.INVALID_PARAMETER
}
# Get the diff content from the specified git command
nu -c $cmd
}
# Apply file filters to the diff content to include or exclude specific files
def apply-file-filters [
content: string, # The diff content to filter
--include: string, # Comma separated file patterns to include in the code review
--exclude: string, # Comma separated file patterns to exclude in the code review
] {
mut filtered_content = $content
let awk_bin = (prepare-awk)
let outdated_awk = $'If you are using an (ansi r)outdated awk version(ansi reset), please upgrade to the latest version or use gawk latest instead.'
if ($include | is-not-empty) {
let patterns = $include | split row ','
$filtered_content = $filtered_content | try {
^$awk_bin (generate-include-regex $patterns)
} catch {
print $outdated_awk
exit $ECODE.OUTDATED
}
}
if ($exclude | is-not-empty) {
let patterns = $exclude | split row ','
$filtered_content = $filtered_content | try {
^$awk_bin (generate-exclude-regex $patterns)
} catch {
print $outdated_awk
exit $ECODE.OUTDATED
}
}
$filtered_content
}

187
nu/kv.nu Normal file
View File

@@ -0,0 +1,187 @@
# NOTE: This file was copied from https://github.com/nushell/nushell/tree/main/crates/nu-std/std-rfc/kv
# And will be removed after the Nu v0.103 release
# More examples could be found here: https://github.com/nushell/nushell/discussions/14965
#
# kv module
#
# use std-rfc/kv *
#
# Easily store and retrieve key-value pairs
# in a pipeline.
#
# A common request is to be able to assign a
# pipeline result to a variable. While it's
# not currently possible to use a "let" statement
# within a pipeline, this module provides an
# alternative. Think of each key as a variable
# that can be set and retrieved.
# Stores the pipeline value for later use
#
# If the key already exists, it is updated to the new value provided.
export def "kv set" [
key: string
value_or_closure?: any
--return (-r): string # Whether and what to return to the pipeline output
--universal (-u)
] {
# Pipeline input is preferred, but prioritize
# parameter if present. This allows $in to be
# used in the parameter if needed.
let input = $in
# If passed a closure, execute it
let arg_type = ($value_or_closure | describe)
let value = match $arg_type {
closure => { $input | do $value_or_closure }
_ => ($value_or_closure | default $input)
}
# Store values as nuons for type-integrity
let kv_pair = {
session: '' # Placeholder
key: $key
value: ($value | to nuon)
}
let db_open = (db_setup --universal=$universal)
try {
# Delete the existing key if it does exist
do $db_open | query db "DELETE FROM std_kv_store WHERE key = :key" --params { key: $key }
}
match $universal {
true => { $kv_pair | into sqlite (universal_db_path) -t std_kv_store }
false => { $kv_pair | stor insert -t std_kv_store }
}
# The value that should be returned from `kv set`
# By default, this is the input to `kv set`, even if
# overridden by a positional parameter.
# This can also be:
# input: (Default) The pipeline input to `kv set`, even if
# overridden by a positional parameter. `null` if no
# pipeline input was used.
# ---
# value: If a positional parameter was used for the value, then
# return it, otherwise return the input (whatever was set).
# If the positional was a closure, return the result of the
# closure on the pipeline input.
# ---
# all: The entire contents of the existing kv table are returned
match ($return | default 'input') {
'all' => (kv list --universal=$universal)
'a' => (kv list --universal=$universal)
'value' => $value
'v' => $value
'input' => $input
'in' => $input
'i' => $input
_ => {
error make {
msg: "Invalid --return option"
label: {
text: "Must be 'all'/'a', 'value'/'v', or 'input'/'in'/'i'"
span: (metadata $return).span
}
}
}
}
}
# Retrieves a stored value by key
#
# Counterpart of "kv set". Returns null if the key is not found.
export def "kv get" [
key: string # Key of the kv-pair to retrieve
--universal (-u)
] {
let db_open = (db_setup --universal=$universal)
do $db_open
# Hack to turn a SQLiteDatabase into a table
| $in.std_kv_store | wrap temp | get temp
| where key == $key
# Should only be one occurrence of each key in the stor
| get -i value.0
| match $in {
# Key not found
null => null
# Key found
_ => { from nuon }
}
}
# List the currently stored key-value pairs
#
# Returns results as the Nushell value rather than the stored nuon.
export def "kv list" [
--universal (-u)
] {
let db_open = (db_setup --universal=$universal)
do $db_open | $in.std_kv_store? | each {|kv_pair|
{
key: $kv_pair.key
value: ($kv_pair.value | from nuon )
}
}
}
# Returns and removes a key-value pair
export def --env "kv drop" [
key: string # Key of the kv-pair to drop
--universal (-u)
] {
let db_open = (db_setup --universal=$universal)
let value = (kv get --universal=$universal $key)
try {
do $db_open
# Hack to turn a SQLiteDatabase into a table
| query db "DELETE FROM std_kv_store WHERE key = :key" --params { key: $key }
}
if $universal and ($env.NU_KV_UNIVERSALS? | default false) {
hide-env $key
}
$value
}
def universal_db_path [] {
$env.NU_UNIVERSAL_KV_PATH?
| default (
$nu.data-dir | path join "std_kv_variables.sqlite3"
)
}
def db_setup [
--universal
] : nothing -> closure {
try {
match $universal {
true => {
# Ensure universal sqlite db and table exists
let uuid = (random uuid)
let dummy_record = {
session: ''
key: $uuid
value: ''
}
$dummy_record | into sqlite (universal_db_path) -t std_kv_store
open (universal_db_path) | query db "DELETE FROM std_kv_store WHERE key = :key" --params { key: $uuid }
}
false => {
# Create the stor table if it doesn't exist
stor create -t std_kv_store -c {session: str, key: str, value: str} | ignore
}
}
}
# Return the correct closure for opening on-disk vs. in-memory
match $universal {
true => {{|| open (universal_db_path)}}
false => {{|| stor open}}
}
}

372
nu/review.nu Executable file → Normal file
View File

@@ -25,18 +25,15 @@
# - Local Repo Review: just cr -f HEAD~1 --debug
# - Local PR Review: just cr -r hustcer/deepseek-review -n 32
use std-rfc/kv *
use diff.nu [get-diff]
use kv.nu *
use common.nu [
ECODE, NO_TOKEN_TIP, hr-line, is-installed, windows?, mac?,
compare-ver, compact-record, git-check, has-ref, GITHUB_API_BASE
ECODE, hr-line, is-installed, windows?, mac?,
compare-ver, compact-record, git-check, has-ref,
]
const IGNORED_MESSAGES = {
'-alive': true, # The server is alive
'data: [DONE]': true, # The end of the response
': OPENROUTER PROCESSING': true, # OPENROUTER in PROCESSING message
}
const RESPONSE_END = 'data: [DONE]'
const GITHUB_API_BASE = 'https://api.github.com'
# It takes longer to respond to requests made with unknown/rare user agents.
# When make http post pretend to be curl, it gets a response just as quickly as curl.
@@ -50,12 +47,14 @@ const DEFAULT_OPTIONS = {
SYS_PROMPT: 'You are a professional code review assistant responsible for analyzing code changes in GitHub Pull Requests. Identify potential issues such as code style violations, logical errors, security vulnerabilities, and provide improvement suggestions. Clearly list the problems and recommendations in a concise manner.',
}
# If the PR title or body contains any of these keywords, skip the review
const IGNORE_REVIEW_KEYWORDS = ['skip review' 'skip cr']
# Use DeepSeek AI to review code changes locally or in GitHub Actions
export def --env deepseek-review [
token?: string, # Your DeepSeek API token, fallback to CHAT_TOKEN env var
--debug(-d), # Debug mode
--repo(-r): string, # GitHub repo name, e.g. hustcer/deepseek-review, or local repo path / alias
--output(-o): string, # Output file path
--pr-number(-n): string, # GitHub PR number
--gh-token(-k): string, # Your GitHub token, fallback to GITHUB_TOKEN env var
--diff-to(-t): string, # Diff to git REF
@@ -64,7 +63,6 @@ export def --env deepseek-review [
--max-length(-l): int, # Maximum length of the content for review, 0 means no limit.
--model(-m): string, # Model name, or read from CHAT_MODEL env var, `deepseek-chat` by default
--base-url(-b): string, # DeepSeek API base URL, fallback to BASE_URL env var
--chat-url(-U): string, # DeepSeek Model chat full API URL, e.g. http://localhost:11535/api/chat
--sys-prompt(-s): string # Default to $DEFAULT_OPTIONS.SYS_PROMPT,
--user-prompt(-u): string # Default to $DEFAULT_OPTIONS.USER_PROMPT,
--include(-i): string, # Comma separated file patterns to include in the code review
@@ -73,22 +71,21 @@ export def --env deepseek-review [
]: nothing -> nothing {
$env.config.table.mode = 'psql'
let local_repo = $env.PWD
let write_file = ($output | is-not-empty)
let is_action = ($env.GITHUB_ACTIONS? == 'true')
let stream = if $is_action { false } else { true }
let token = $token | default $env.CHAT_TOKEN?
let repo = $repo | default $env.DEFAULT_GITHUB_REPO?
let CHAT_HEADER = [Authorization $'Bearer ($token)']
let stream = if $is_action or $write_file { false } else { true }
let local_repo = $env.DEFAULT_LOCAL_REPO? | default (pwd)
let model = $model | default $env.CHAT_MODEL? | default $DEFAULT_OPTIONS.MODEL
let base_url = $base_url | default $env.BASE_URL? | default $DEFAULT_OPTIONS.BASE_URL
let url = $chat_url | default $env.CHAT_URL? | default $'($base_url)/chat/completions'
let max_length = try { $max_length | default ($env.MAX_LENGTH? | default 0 | into int) } catch { 0 }
let temperature = try { $temperature | default $env.TEMPERATURE? | default $DEFAULT_OPTIONS.TEMPERATURE | into float } catch { $DEFAULT_OPTIONS.TEMPERATURE }
# Determine output mode
let output_mode = if $is_action { 'action' } else if ($output | is-not-empty) { 'file' } else { 'console' }
validate-temperature $temperature
if ($temperature < 0) or ($temperature > 2) {
print $'(ansi r)Invalid temperature value, should be in the range of 0 to 2.(ansi reset)'
exit $ECODE.INVALID_PARAMETER
}
let url = $'($base_url)/chat/completions'
let setting = {
repo: $repo,
model: $model,
@@ -105,7 +102,10 @@ export def --env deepseek-review [
}
$env.GH_TOKEN = $gh_token | default $env.GITHUB_TOKEN?
validate-token $token --pr-number $pr_number --repo $repo
if ($token | is-empty) {
print $'(ansi r)Please provide your DeepSeek API token by setting `CHAT_TOKEN` or passing it as an argument.(ansi reset)'
exit $ECODE.INVALID_PARAMETER
}
let hint = if not $is_action and ($pr_number | is-empty) {
$'🚀 Initiate the code review by DeepSeek AI for local changes ...'
} else {
@@ -138,12 +138,12 @@ export def --env deepseek-review [
]
}
if $debug { print $'(char nl)Code Changes:'; hr-line; print $content }
print $'(char nl)Waiting for response from (ansi g)($url)(ansi reset) ...'
print $'(char nl)Waiting for response from (ansi g)($base_url)(ansi reset) ...'
if $stream { streaming-output $url $payload --headers $CHAT_HEADER --debug=$debug; return }
let response = http post -e -H $CHAT_HEADER -t application/json $url $payload
if ($response | is-empty) {
print $'(ansi r)Oops, No response returned from ($url) ...(ansi reset)'
print $'(ansi r)Oops, No response returned from ($base_url) ...(ansi reset)'
exit $ECODE.SERVER_ERROR
}
if $debug { print $'DeepSeek Model Response:'; hr-line; $response | table -e | print }
@@ -151,93 +151,23 @@ export def --env deepseek-review [
print $'✖️ Code review failedError: '; hr-line; print $response
exit $ECODE.SERVER_ERROR
}
let message = $response | get -i choices.0.message
let reason = $message | coalesce-reasoning
let review = $message.content? | default ($response | get -i message.content)
let reason = $response | get -i choices.0.message.reasoning_content
let review = $response | get -i choices.0.message.content
let result = ['<details>' '<summary> Reasoning Details</summary>' $reason "</details>\n" $review] | str join "\n"
if ($review | is-empty) {
print $'✖️ Code review failedNo review result returned from ($base_url) ...'
exit $ECODE.SERVER_ERROR
}
let result = if ($reason | is-empty) { $review } else { $result }
match $output_mode {
'action' => {
post-comments-to-pr $repo $pr_number $result
print $'✅ Code review finishedPR (ansi g)#($pr_number)(ansi reset) review result was posted as a comment.'
}
'file' => { write-review-to-file $output $setting $result $response }
_ => { print $'Code Review Result:'; hr-line; print $result }
}
if ($response.usage? | is-not-empty) {
print $'(char nl)Token Usage:'; hr-line
$response.usage? | table -e | print
}
}
# Write the code review result to a file
def write-review-to-file [
file: string, # Output file path
setting: record, # Review settings
result: string, # Review result
response: record, # DeepSeek API response
] {
let file = (if not ($file | str ends-with '.md') { $'($file).md' } else { $file })
let token_usage = if ($response.usage? | is-empty) { [] } else {
['## Token Usage', '', ($response.usage? | transpose key val | to md --pretty)]
}
# Generate content sections
let content_sections = [
'# DeepSeek Code Review Result', ''
$"Generated at: (date now | format date '%Y/%m/%d %H:%M:%S')", ''
'## Code Review Settings', ''
($setting | compact-record | reject -i repo | transpose key val | to md --pretty)
'', '## Review Detail', '', $result, '', ...$token_usage
]
try {
$content_sections | str join (char nl) | save --force $file
print $'Code Review Result saved to (ansi g)($file)(ansi reset)'
} catch {|err|
print $'(ansi r)Failed to save review result: (ansi reset)'
$err | table -e | print
}
}
# Validate the DeepSeek API token
def validate-token [token?: string, --pr-number: string, --repo: string] {
if ($token | is-empty) {
print $'(ansi r)Please provide your DeepSeek API token by setting `CHAT_TOKEN` or passing it as an argument.(ansi reset)'
if ($pr_number | is-not-empty) { post-comments-to-pr $repo $pr_number $NO_TOKEN_TIP }
exit $ECODE.INVALID_PARAMETER
}
$token
}
# Validate the temperature value
def validate-temperature [temp: float] {
if ($temp < 0) or ($temp > 2) {
print $'(ansi r)Invalid temperature value, should be in the range of 0 to 2.(ansi reset)'
exit $ECODE.INVALID_PARAMETER
}
$temp
}
# Post review comments to GitHub PR
def post-comments-to-pr [
repo: string, # GitHub repository name, e.g. hustcer/deepseek-review
pr_number: string, # GitHub PR number
comments: string, # Comments content to post
] {
let comment_url = $'($GITHUB_API_BASE)/repos/($repo)/issues/($pr_number)/comments'
let BASE_HEADER = [Authorization $'Bearer ($env.GH_TOKEN)' Accept application/vnd.github.v3+json ...$HTTP_HEADERS]
try {
http post -t application/json -H $BASE_HEADER $comment_url { body: $comments }
} catch {|err|
print $'(ansi r)Failed to post comments to PR: (ansi reset)'
$err | table -e | print
exit $ECODE.SERVER_ERROR
if not $is_action {
print $'Code Review Result:'; hr-line; print $result
} else {
let BASE_HEADER = [Authorization $'Bearer ($env.GH_TOKEN)' Accept application/vnd.github.v3+json ...$HTTP_HEADERS]
http post -t application/json -H $BASE_HEADER $'($GITHUB_API_BASE)/repos/($repo)/issues/($pr_number)/comments' { body: $result }
print $'✅ Code review finishedPR (ansi g)#($pr_number)(ansi reset) review result was posted as a comment.'
}
print $'(char nl)Token Usage:'; hr-line
$response.usage | table -e | print
}
# Output the streaming response of review result from DeepSeek API
@@ -255,7 +185,7 @@ def streaming-output [
let res = $in
let type = $res | describe
let record_error = $type =~ '^record'
let other_error = $type =~ '^string' and $res !~ 'data: ' and $res !~ 'done'
let other_error = $type =~ '^string' and $res !~ 'data: '
if $record_error or $other_error {
$res | table -e | print
exit $ECODE.SERVER_ERROR
@@ -263,17 +193,18 @@ def streaming-output [
}
| try { lines } catch { print $'(ansi r)Error Happened ...(ansi reset)'; exit $ECODE.SERVER_ERROR }
| each {|line|
if $line == $RESPONSE_END { return }
if ($line | is-empty) { return }
if ($IGNORED_MESSAGES | get -i $line | default false) { return }
let $last = $line | parse-line
let $last = $line | str substring 6.. | from json
if $last == '-alive' { print $last; return }
if $debug { $last | to json | kv set last-reply }
$last | get -i choices.0.delta | default ($last | get -i message) | if ($in | is-not-empty) {
$last | get -i choices.0.delta | if ($in | is-not-empty) {
let delta = $in
if ($delta | coalesce-reasoning | is-not-empty) { kv set reasoning ((kv get reasoning) + 1) }
if ($delta.reasoning_content? | is-not-empty) { kv set reasoning ((kv get reasoning) + 1) }
if (kv get reasoning) == 1 { print $'(char nl)Reasoning Details:'; hr-line }
if ($delta.content | is-not-empty) { kv set content ((kv get content) + 1) }
if (kv get content) == 1 { print $'(char nl)Review Details:'; hr-line }
print -n ($delta | coalesce-reasoning | default $delta.content)
print -n ($delta.reasoning_content? | default $delta.content)
}
}
@@ -283,26 +214,219 @@ def streaming-output [
}
}
# Parse the line from the streaming response
def parse-line [] {
let $line = $in
# DeepSeek Response vs Local Ollama Response
try {
if $line =~ '^data: ' {
$line | str substring 6.. | from json
} else {
$line | from json
# Get the diff content from GitHub PR or local git changes
export def get-diff [
--repo: string, # GitHub repository name
--pr-number: string, # GitHub PR number
--diff-to: string, # Diff to git ref
--diff-from: string, # Diff from git ref
--include: string, # Comma separated file patterns to include in the code review
--exclude: string, # Comma separated file patterns to exclude in the code review
--patch-cmd: string, # The `git show` or `git diff` command to get the diff content
] {
let BASE_HEADER = [Authorization $'Bearer ($env.GH_TOKEN)' Accept application/vnd.github.v3+json]
let DIFF_HEADER = [Authorization $'Bearer ($env.GH_TOKEN)' Accept application/vnd.github.v3.diff]
let local_repo = $env.DEFAULT_LOCAL_REPO? | default (pwd)
if ($pr_number | is-empty) {
if not ($local_repo | path exists) {
print $'(ansi r)The directory ($local_repo) does not exist.(ansi reset)'
exit $ECODE.CONDITION_NOT_SATISFIED
}
} catch {
print -e $'(ansi r)Unrecognized content:(ansi reset) ($line)'
exit $ECODE.SERVER_ERROR
cd $local_repo
}
mut content = if ($pr_number | is-not-empty) {
if ($repo | is-empty) {
print $'(ansi r)Please provide the GitHub repository name by `--repo` option.(ansi reset)'
exit $ECODE.INVALID_PARAMETER
}
# TODO: Ignore keywords checking when triggering by mentioning the bot
let description = http get -H $BASE_HEADER $'($GITHUB_API_BASE)/repos/($repo)/pulls/($pr_number)'
| select title body | values | str join "\n"
if ($IGNORE_REVIEW_KEYWORDS | any {|it| $description =~ $it }) {
print $'(ansi r)The PR title or body contains keywords to skip the review, bye...(ansi reset)'
exit $ECODE.SUCCESS
}
http get -H $DIFF_HEADER $'($GITHUB_API_BASE)/repos/($repo)/pulls/($pr_number)' | str trim
} else if ($diff_from | is-not-empty) {
if not (has-ref $diff_from) {
print $'(ansi r)The specified git ref ($diff_from) does not exist, please check it again.(ansi reset)'
exit $ECODE.INVALID_PARAMETER
}
if ($diff_to | is-not-empty) and not (has-ref $diff_to) {
print $'(ansi r)The specified git ref ($diff_to) does not exist, please check it again.(ansi reset)'
exit $ECODE.INVALID_PARAMETER
}
git diff $diff_from ($diff_to | default HEAD)
} else if not (git-check $local_repo --check-repo=1) {
print $'Current directory ($local_repo) is (ansi r)NOT(ansi reset) a git repo, bye...(char nl)'
exit $ECODE.CONDITION_NOT_SATISFIED
} else if ($patch_cmd | is-not-empty) {
let valid = is-safe-git $patch_cmd
if not $valid { exit $ECODE.INVALID_PARAMETER }
nu -c $patch_cmd
} else { git diff }
if ($content | is-empty) {
print $'(ansi g)Nothing to review.(ansi reset)'; exit $ECODE.SUCCESS
}
let awk_bin = (prepare-awk)
let outdated_awk = $'If you are using an (ansi r)outdated awk version(ansi reset), please upgrade to the latest version or use gawk latest instead.'
if ($include | is-not-empty) {
let patterns = $include | split row ','
$content = $content | try { ^$awk_bin (generate-include-regex $patterns) } catch { print $outdated_awk; exit $ECODE.OUTDATED }
}
if ($exclude | is-not-empty) {
let patterns = $exclude | split row ','
$content = $content | try { ^$awk_bin (generate-exclude-regex $patterns) } catch { print $outdated_awk; exit $ECODE.OUTDATED }
}
$content
}
# Coalesce the reasoning content
def coalesce-reasoning [] {
let msg = $in
$msg.reasoning_content? | default $msg.reasoning?
# AWK family version check for both awk and gawk
# awk: awk version 20250116 -> 20250116
# gawk: GNU Awk 5.3.1, API 4.0, (GNU MPFR 4.2.1, GNU MP 6.3.0) -> 5.3.1
def get-awk-ver [awk_bin: string] {
^$awk_bin --version | lines | first | split row , | first | split row ' ' | last
}
# Prepare gawk for macOS
export def prepare-awk [] {
const MIN_GAWK_VERSION = '5.3.1'
const MIN_AWK_VERSION = '20250116'
let awk_installed = is-installed awk
let gawk_installed = is-installed gawk
if $awk_installed {
let awk_version = get-awk-ver awk
if (compare-ver $awk_version $MIN_AWK_VERSION) >= 0 {
print $'Current awk version: ($awk_version)'
return 'awk'
}
}
if $gawk_installed {
let gawk_version = get-awk-ver gawk
if (compare-ver $gawk_version $MIN_GAWK_VERSION) >= 0 {
print $'Current gawk version: ($gawk_version)'
return 'gawk'
} else if (windows?) and ($env.GITHUB_ACTIONS? == 'true') {
let awk_info = (install-gawk-for-actions)
print $'Current gawk version: ($awk_info.version)'
return $awk_info.awk_bin
}
}
if (mac?) and (is-installed brew) {
brew install gawk
print $'Current gawk version: (get-awk-ver gawk)'
return 'gawk'
}
if (not $awk_installed) and (not $gawk_installed) {
print $'(ansi r)Neither `awk` nor `gawk` is installed, please install the latest version of `gawk`.(ansi reset)'
exit $ECODE.MISSING_BINARY
}
print $'Current awk version: (get-awk-ver awk)'
'awk'
}
# Convert glob patterns to regex patterns
# Pass in *.nu directly as a regular expression does not work, because * in
# a regular expression needs to be attached to the previous pattern, the correct
# form should be .* So we should convert each glob pattern to a regex pattern:
# 1. Convert * to .*
# 2. Convert ? to . (optional, as needed)
# 3. Convert / to \/
def glob-to-regex [patterns: list<string>] {
$patterns
| each { |pat|
$pat | str replace "*" ".*" | str replace "?" "." | str replace "/" "\\/"
}
| str join "|"
}
# Generate the awk include regex pattern string for the specified patterns
export def generate-include-regex [patterns: list<string>] {
let pattern = glob-to-regex $patterns
$"/^diff --git/{p=/^diff --git a\\/($pattern)/}p"
}
# Generate the awk exclude regex pattern string for the specified patterns
export def generate-exclude-regex [patterns: list<string>] {
let pattern = glob-to-regex $patterns
$"/^diff --git/{p=/^diff --git a\\/($pattern)/}!p"
}
# Check if the git command is safe to run in the shell
# Validate command examples:
# - git show
# - git diff
# - git show head~1
# - git diff --since=2025-02-09 HEAD
# - git diff 2393375 71f5a31
# - git diff 2393375 71f5a31 nu/*
# - git diff 2393375 71f5a31 :!nu/*
export def is-safe-git [cmd: string] {
# Normalize the command string by trimming and converting to lowercase
let normalized_cmd = ($cmd | str trim | str downcase)
# More strict regex for git commands, allow:
# 1. --since parameter with ISO date format
# 2. File path patterns with or without colon (e.g. :!nu/*, nu/*)
let allowed_regex = '^git\s+(show|diff)(?:\s+(?:--since=\d{4}-\d{2}-\d{2}|[a-zA-Z0-9_\-\.~/]+))*(?:\s+(?::[!]?)?[a-zA-Z0-9_\-\.\*\/]+)?$'
# Dangerous patterns to check (expanded list)
let dangerous_patterns = [
# Command chaining/injection
';', '&&', '||', '|',
# Shell expansion
'?', '[', ']', '{', '}',
# Command substitution
'`', '$(',
# IO redirection
'>', '>>', '<', '<<',
# Special characters
'\n', '\r', '\t',
# Path traversal
'..',
# Environment variables
'$', '%',
# Quotes that might be used for injection
'"', "'"
]
# First check: Command must match the allowed pattern
if ($normalized_cmd | find -r $allowed_regex | is-empty) {
print $'ERROR: Invalid git command format. (ansi r)Only simple `git show` or `git diff` commands are allowed(ansi reset).'
return false
}
# Second check: No dangerous patterns allowed
for pattern in $dangerous_patterns {
if ($cmd | str contains $pattern) {
print $'(ansi r)ERROR: Dangerous pattern detected: `($pattern)`(ansi reset)'
return false
}
}
# Third check: Command parts validation (increased limit to accommodate path patterns)
let cmd_parts = $normalized_cmd | split row ' '
if ($cmd_parts | length) > 6 {
print $'ERROR: Command too complex. (ansi r)Only simple `git show` or `git diff` commands are allowed(ansi reset).'
return false
}
true
}
# Setup scoop and install gawk for GitHub Windows runners
# This command is essential for resolving the issue of simultaneously
# applying include and exclude patterns on GitHub's Windows runners.
def install-gawk-for-actions [] {
# Install scoop using PowerShell
pwsh -c r#'
Invoke-Expression (New-Object System.Net.WebClient).DownloadString("https://get.scoop.sh")
$env:Path = "$env:USERPROFILE\scoop\shims;" + $env:Path; scoop update; scoop install gawk
'# | complete | get stdout | print
let awk_bin = $'($nu.home-path)/scoop/shims/gawk.exe'
let version = get-awk-ver $awk_bin
{ awk_bin: $awk_bin, version: $version }
}
alias main = deepseek-review

View File

@@ -1,138 +0,0 @@
#!/usr/bin/env nu
# Author: hustcer
# Created: 2025/04/02 20:02:15
#
use common.nu [ECODE, is-installed, compare-ver, windows?, mac?]
# AWK family version check for both awk and gawk
# awk: awk version 20250116 -> 20250116
# gawk: GNU Awk 5.3.1, API 4.0, (GNU MPFR 4.2.1, GNU MP 6.3.0) -> 5.3.1
def get-awk-ver [awk_bin: string] {
^$awk_bin --version | lines | first | split row , | first | split row ' ' | last
}
# Prepare gawk for macOS
export def prepare-awk [] {
const MIN_GAWK_VERSION = '5.3.1'
const MIN_AWK_VERSION = '20250116'
let awk_installed = is-installed awk
let gawk_installed = is-installed gawk
if $awk_installed {
let awk_version = get-awk-ver awk
if (compare-ver $awk_version $MIN_AWK_VERSION) >= 0 {
print $'Current awk version: ($awk_version)'
return 'awk'
}
}
if $gawk_installed {
let gawk_version = get-awk-ver gawk
if (compare-ver $gawk_version $MIN_GAWK_VERSION) >= 0 {
print $'Current gawk version: ($gawk_version)'
return 'gawk'
} else if (windows?) and ($env.GITHUB_ACTIONS? == 'true') {
let awk_info = (install-gawk-for-actions)
print $'Current gawk version: ($awk_info.version)'
return $awk_info.awk_bin
}
}
if (mac?) and (is-installed brew) {
brew install gawk
print $'Current gawk version: (get-awk-ver gawk)'
return 'gawk'
}
if (not $awk_installed) and (not $gawk_installed) {
print $'(ansi r)Neither `awk` nor `gawk` is installed, please install the latest version of `gawk`.(ansi reset)'
exit $ECODE.MISSING_BINARY
}
print $'Current awk version: (get-awk-ver awk)'
'awk'
}
# Convert glob patterns to regex patterns
# Pass in *.nu directly as a regular expression does not work, because * in
# a regular expression needs to be attached to the previous pattern, the correct
# form should be .* So we should convert each glob pattern to a regex pattern:
# 1. Convert * to .*
# 2. Convert ? to . (optional, as needed)
# 3. Convert / to \/
def glob-to-regex [patterns: list<string>] {
# Handle empty patterns list
if ($patterns | length) == 0 { return '' }
# Define a mapping of characters to escape
let regex_escapes = {
# Escape special regex characters first
"\\.": "\\\\.",
"\\+": "\\\\+",
"\\^": "\\\\^",
"\\$": "\\\\$",
"\\(": "\\\\(",
"\\)": "\\\\)",
"\\[": "\\\\[",
"\\]": "\\\\]",
"\\{": "\\\\{",
"\\}": "\\\\}",
"\\|": "\\\\|",
# Then convert glob patterns to regex patterns
"*": ".*",
"?": ".",
"/": "\\/",
}
$patterns
| each { |pat|
$regex_escapes | columns | reduce -f $pat { |k, acc|
$acc | str replace -a $k ($regex_escapes | get $k)
}
}
| str join '|'
}
# Generate the awk include regex pattern string for the specified patterns
export def generate-include-regex [patterns: list<string>] {
let pattern = glob-to-regex $patterns
$"/^diff --git/{p=/^diff --git a\\/($pattern)/}p"
}
# Generate the awk exclude regex pattern string for the specified patterns
export def generate-exclude-regex [patterns: list<string>] {
let pattern = glob-to-regex $patterns
$"/^diff --git/{p=/^diff --git a\\/($pattern)/}!p"
}
# Check if the git command is safe to run in the shell
# Validate command examples:
# - git show
# - git diff
# - git show head~1
# - git diff 2393375 71f5a31
# - git diff 2393375 71f5a31 nu/*
# - git diff 2393375 71f5a31 :!nu/*
export def is-safe-git [cmd: string] {
let normalized_cmd = ($cmd | str trim | str downcase)
# Define allowed command patterns with named capture groups for better validation
let git_cmd_pattern = '^git\s+(show|diff)(?:\s+(?:[a-zA-Z0-9_\-\.~/]+(?::[a-zA-Z0-9_\-\.\*\/]+)?)){0,3}(?:\s+(?::[!]?)?[a-zA-Z0-9_\-\.\*\/]+){0,2}$'
if ($normalized_cmd | find -r $git_cmd_pattern | is-empty) {
print $'(ansi r)Invalid git command format. (ansi g)Only simple `git show` or `git diff` commands are allowed.(ansi reset)'
return false
}
true
}
# Setup scoop and install gawk for GitHub Windows runners
# This command is essential for resolving the issue of simultaneously
# applying include and exclude patterns on GitHub's Windows runners.
def install-gawk-for-actions [] {
# Install scoop using PowerShell
pwsh -c r#'
Invoke-Expression (New-Object System.Net.WebClient).DownloadString("https://get.scoop.sh")
$env:Path = "$env:USERPROFILE\scoop\shims;" + $env:Path; scoop update; scoop install gawk
'# | complete | get stdout | print
let awk_bin = $'($nu.home-path)/scoop/shims/gawk.exe'
let version = get-awk-ver $awk_bin
{ awk_bin: $awk_bin, version: $version }
}

View File

@@ -1,10 +1,7 @@
use std/assert
use ../nu/common.nu [
compare-ver, 'from env', is-installed, has-ref,
git-check, compact-record, is-repo, windows?, mac?,
]
use ../nu/common.nu [compare-ver, 'from env', is-installed, has-ref, git-check, compact-record]
#[test]
def 'compare-verv1.0.0 is greater than v0.999.0' [] {
@@ -55,11 +52,6 @@ def 'has-refgit repo should has HEAD ref' [] {
assert equal (has-ref 0000) false
}
#[test]
def 'is-repocurrent dir is a git repo' [] {
assert equal (is-repo) true
}
#[test]
def 'git-checkcurrent dir is a git repo' [] {
assert equal (git-check (pwd) --check-repo=1) true
@@ -70,31 +62,3 @@ def 'compact-recordshould work as expected' [] {
assert equal ({a: null, b: '', c: 'abc' } | compact-record) { c: 'abc' }
assert equal ({a: null, b: 0, c: 1, e: { f: 'g' } } | compact-record) { b: 0, c: 1, e: { f: 'g' } }
}
#[test]
def 'OS check should work as expected' [] {
# `$env.RUNNER_OS` Possible values are Linux, Windows, or macOS in GitHub Actions
match $nu.os-info.name {
'windows' => {
assert equal (mac?) false
assert equal (windows?) true
if ($env.RUNNER_OS? | is-not-empty) {
assert equal $env.RUNNER_OS Windows
}
}
'macos' => {
assert equal (mac?) true
assert equal (windows?) false
if ($env.RUNNER_OS? | is-not-empty) {
assert equal $env.RUNNER_OS macOS
}
}
_ => {
assert equal (mac?) false
assert equal (windows?) false
if ($env.RUNNER_OS? | is-not-empty) {
assert equal $env.RUNNER_OS Linux
}
}
}
}

View File

@@ -1,7 +1,10 @@
use std/assert
use ../nu/diff.nu [get-diff]
use ../nu/util.nu [is-safe-git, prepare-awk, generate-include-regex, generate-exclude-regex]
use ../nu/review.nu [
get-diff, is-safe-git, prepare-awk,
generate-include-regex, generate-exclude-regex,
]
# Get the unicode width of the input string
def get-uw [] { $in | str stats | get unicode-width }
@@ -22,13 +25,9 @@ def 'is-safe-gitshould work as expected' [] {
assert equal (is-safe-git 'git checkout') false
assert equal (is-safe-git 'git show 0dd0eb5') true
assert equal (is-safe-git 'git show HEAD') true
assert equal (is-safe-git 'git show head~1') true
assert equal (is-safe-git 'git diff HEAD~2') true
assert equal (is-safe-git 'git diff head~3 main') true
assert equal (is-safe-git 'git diff f536acc 0dd0eb5') true
assert equal (is-safe-git 'git show 2393375 | less') false
assert equal (is-safe-git 'git show 2393375>diff.patch') false
assert equal (is-safe-git 'git show 2393375 o+e>diff.patch') false
assert equal (is-safe-git 'git diff f536acc 0dd0eb5 nu/*') true
assert equal (is-safe-git 'git diff f536acc 0dd0eb5 :!nu/*') true
assert equal (is-safe-git 'git diff f536acc 0dd0eb5 :!nu/*; rm -rf abc') false
@@ -41,8 +40,6 @@ def 'is-safe-gitshould work as expected' [] {
assert equal (is-safe-git 'git diff f536acc 0dd0eb5 :!nu/* >> out.txt') false
assert equal (is-safe-git 'git diff f536acc 0dd0eb5 :!nu/* < in.txt') false
assert equal (is-safe-git 'git diff f536acc 0dd0eb5 :!nu/* << in.txt') false
assert equal (is-safe-git 'git show head:nu/common.nu') true
assert equal (is-safe-git 'git show HEAD:nu/common.nu') true
}
#[test]