test(feishu): add local setup and smoke scripts

This commit is contained in:
whzy 2026-06-26 16:28:57 +08:00
parent 8bcc8607f5
commit 73da46c143
13 changed files with 906 additions and 116 deletions

6
.gitignore vendored
View File

@ -1,3 +1,9 @@
gitlink-cli.exe
/gitlink-cli
.local/*
!.local/
!.local/feishu-gitlink.env.example.ps1
*.local.ps1
*.secret.*
reports/feishu-real-smoke-terminal.log

View File

@ -0,0 +1,30 @@
# Stable Feishu webhook
$env:FEISHU_WEBHOOK_URL=""
$env:FEISHU_WEBHOOK_SECRET=""
# Feishu Open Platform
$env:FEISHU_APP_ID=""
$env:FEISHU_APP_SECRET=""
# Feishu DocX / Wiki
$env:FEISHU_WIKI_URL=""
$env:FEISHU_WIKI_NODE_TOKEN=""
$env:FEISHU_FOLDER_TOKEN=""
# Feishu Base / Bitable
$env:FEISHU_BASE_APP_TOKEN=""
$env:FEISHU_REPORT_TABLE_ID=""
$env:FEISHU_ISSUE_TABLE_ID=""
$env:FEISHU_PR_TABLE_ID=""
$env:FEISHU_CONTRIBUTOR_TABLE_ID=""
$env:FEISHU_TASK_TABLE_ID=""
# Feishu Task
$env:FEISHU_TASK_PROJECT_ID=""
$env:FEISHU_TASK_SECTION_ID=""
# GitLink real test input
$env:GITLINK_OWNER=""
$env:GITLINK_REPO=""
$env:GITLINK_TEST_PR_IDS=""
$env:GITLINK_TOKEN=""

View File

@ -686,6 +686,16 @@ Details:
- [Feishu environment variables](./docs/FEISHU_ENVIRONMENT.md)
- [Feishu permission matrix](./reports/FEISHU_PERMISSION_MATRIX.md)
Local setup and smoke testing:
```powershell
.\scripts\feishu-gitlink-setup.ps1
.\scripts\feishu-gitlink-env-check.ps1 -Layer stable
.\scripts\feishu-gitlink-smoke.ps1 -Mode preview
```
The setup script stores real values only in `.local/feishu-gitlink.env.ps1`, which is ignored.
### Dataset
`dataset` manages and queries GitLink research datasets (title, description,

View File

@ -586,6 +586,16 @@ gitlink-cli feishu +task-create --from-workflow-json report.json --send --format
- [飞书能力分层](./docs/FEISHU_CAPABILITY_LAYERS.md)
- [飞书环境变量](./docs/FEISHU_ENVIRONMENT.md)
- [飞书权限矩阵](./reports/FEISHU_PERMISSION_MATRIX.md)
本地配置和 smoke 测试:
```powershell
.\scripts\feishu-gitlink-setup.ps1
.\scripts\feishu-gitlink-env-check.ps1 -Layer stable
.\scripts\feishu-gitlink-smoke.ps1 -Mode preview
```
真实值只会写入被忽略的 `.local/feishu-gitlink.env.ps1`,不要提交。
### Raw API
Shortcuts 未覆盖的接口可通过 Raw API 直接调用:

View File

@ -4,6 +4,26 @@ Date: 2026-06-26
Do not commit real values. Use a local shell profile, CI secret store, or test terminal session.
Recommended local workflow:
```powershell
.\scripts\feishu-gitlink-setup.ps1
.\scripts\feishu-gitlink-env-check.ps1 -Layer stable
.\scripts\feishu-gitlink-smoke.ps1 -Mode preview
```
`feishu-gitlink-setup.ps1` opens the relevant Feishu / GitLink pages, lets the user paste values locally, and writes only to:
```text
.local/feishu-gitlink.env.ps1
```
The real local env file is ignored. The tracked example is:
```text
.local/feishu-gitlink.env.example.ps1
```
## Stable Custom Bot Variables
| Name | Purpose | Required | Used by | Sensitive | How to obtain |
@ -112,6 +132,7 @@ $env:GITLINK_TOKEN="REDACTED"
```text
Never paste real secrets into committed docs.
Never paste real secrets into ChatGPT.
Never print raw webhook URLs or app secrets in smoke reports.
Do not commit tenant_access_token or user_access_token.
Do not enable --send in shared scripts unless the target test enterprise is intentional.

View File

@ -6,6 +6,12 @@ This file lists the manual screenshots to capture after local and real smoke tes
Do not fabricate screenshots. If a capability is not available in the test enterprise, keep the placeholder and record the failure in `reports/FEISHU_SMOKE_20260626.md`.
Use the helper to check current screenshot status:
```powershell
.\scripts\feishu-gitlink-screenshot-check.ps1
```
| Screenshot | Expected path | Capture note |
| --- | --- | --- |
| Feishu bot card in test group | `docs/images/feishu-bot-card.png` | Capture after `+bot-test --send` or `+notify --send` |

View File

@ -53,6 +53,15 @@ Experimental commands use Feishu self-built app OpenAPI and are not part of the
Use a Feishu custom group bot for notification cards.
Recommended local setup:
```powershell
.\scripts\feishu-gitlink-setup.ps1
.\scripts\feishu-gitlink-env-check.ps1 -Layer stable
```
The setup wizard opens Feishu / GitLink pages and stores values only in `.local/feishu-gitlink.env.ps1`.
Environment:
```powershell
@ -288,3 +297,12 @@ docs/FEISHU_ENVIRONMENT.md
reports/FEISHU_PERMISSION_MATRIX.md
reports/FEISHU_LOCAL_TESTING_GUIDE.md
```
Scripted smoke test:
```powershell
.\scripts\feishu-gitlink-smoke.ps1 -Mode preview
.\scripts\feishu-gitlink-smoke.ps1 -Mode stable
.\scripts\feishu-gitlink-smoke.ps1 -Mode open-platform
.\scripts\feishu-gitlink-smoke.ps1 -Mode all
```

View File

@ -4,7 +4,48 @@ Date: 2026-06-26
This guide verifies the layered Feishu integration without committing secrets.
## 1. Configure GitLink Test Repository
## 1. Run Local Setup Wizard
```powershell
.\scripts\feishu-gitlink-setup.ps1
```
The setup wizard opens these pages as needed:
```text
Feishu custom bot documentation
Feishu developer console
Feishu Docs / Wiki
Feishu Base / Bitable
Feishu Tasks
GitLink
```
The user logs in and authorizes in the browser. Values are pasted into the local PowerShell prompt, not into ChatGPT.
The wizard writes only to:
```text
.local/feishu-gitlink.env.ps1
```
This file is ignored and must not be committed. A tracked empty example is available at:
```text
.local/feishu-gitlink.env.example.ps1
```
## 2. Check Local Environment
```powershell
.\scripts\feishu-gitlink-env-check.ps1 -Layer stable
.\scripts\feishu-gitlink-env-check.ps1 -Layer open-platform
.\scripts\feishu-gitlink-env-check.ps1 -Layer all
```
The checker prints only redacted values.
## 3. Configure GitLink Test Repository Manually If Needed
```powershell
$env:GITLINK_OWNER="OWNER"
@ -17,7 +58,43 @@ If the current workflow command cannot filter specific PR IDs, keep the PR IDs i
$env:GITLINK_TEST_PR_IDS="1,2,3"
```
## 2. Generate Workflow Report JSON
## 4. Run Scripted Smoke Tests
Preview only:
```powershell
.\scripts\feishu-gitlink-smoke.ps1 -Mode preview
```
Stable custom bot sends:
```powershell
.\scripts\feishu-gitlink-smoke.ps1 -Mode stable
```
Experimental Open Platform sends:
```powershell
.\scripts\feishu-gitlink-smoke.ps1 -Mode open-platform
```
All available tests:
```powershell
.\scripts\feishu-gitlink-smoke.ps1 -Mode all
```
The smoke runner writes:
```text
.local/report.json
reports/FEISHU_SMOKE_YYYYMMDD.md
reports/feishu-real-smoke-terminal.log
```
The terminal log is ignored and must not be committed after real runs.
## 5. Generate Workflow Report JSON Manually
```bash
gitlink-cli workflow +repo-report \
@ -28,13 +105,13 @@ gitlink-cli workflow +repo-report \
Windows PowerShell redirection may produce UTF-16 with BOM. The Feishu workflow JSON reader supports UTF-8 and UTF-16 BOM inputs.
## 3. Preview Feishu Notify Card
## 6. Preview Feishu Notify Card
```bash
gitlink-cli feishu +notify --from-workflow-json report.json --format json
```
## 4. Send Feishu Notify Card
## 7. Send Feishu Notify Card
```bash
gitlink-cli feishu +notify --from-workflow-json report.json --send --format table
@ -47,50 +124,50 @@ FEISHU_WEBHOOK_URL
FEISHU_WEBHOOK_SECRET optional
```
## 5. Render Weekly Report
## 8. Render Weekly Report
```bash
gitlink-cli feishu +weekly-report --from-workflow-json report.json --format markdown
```
## 6. Send Weekly Report
## 9. Send Weekly Report
```bash
gitlink-cli feishu +weekly-report --from-workflow-json report.json --send --format table
```
## 7. Generate Owner Digest
## 10. Generate Owner Digest
```bash
gitlink-cli feishu +owner-digest --from-workflow-json report.json --format markdown
```
## 8. Send Owner Digest
## 11. Send Owner Digest
```bash
gitlink-cli feishu +owner-digest --from-workflow-json report.json --send --format table
```
## 9. Generate Contributor Digest
## 12. Generate Contributor Digest
```bash
gitlink-cli feishu +contributor-digest --from-workflow-json report.json --format markdown
```
## 10. Send Contributor Digest
## 13. Send Contributor Digest
```bash
gitlink-cli feishu +contributor-digest --from-workflow-json report.json --send --format table
```
## 11. Generate Bitable-Ready Records
## 14. Generate Bitable-Ready Records
```bash
gitlink-cli feishu +bitable-schema --tables reports,issues,prs,contributors,tasks --format markdown
gitlink-cli feishu +bitable-records --from-workflow-json report.json --format json
```
## 12. Preview Bitable Sync
## 15. Preview Bitable Sync
```bash
gitlink-cli feishu +bitable-sync \
@ -99,7 +176,7 @@ gitlink-cli feishu +bitable-sync \
--format table
```
## 13. Execute Bitable Sync
## 16. Execute Bitable Sync
```bash
gitlink-cli feishu +bitable-sync \
@ -122,7 +199,7 @@ FEISHU_CONTRIBUTOR_TABLE_ID optional
FEISHU_TASK_TABLE_ID optional
```
## 14. Preview DocX / Wiki Export
## 17. Preview DocX / Wiki Export
```bash
gitlink-cli feishu +doc-export \
@ -131,7 +208,7 @@ gitlink-cli feishu +doc-export \
--format markdown
```
## 15. Execute DocX / Wiki Export
## 18. Execute DocX / Wiki Export
```bash
gitlink-cli feishu +doc-export \
@ -141,13 +218,13 @@ gitlink-cli feishu +doc-export \
--format table
```
## 16. Preview Feishu Tasks
## 19. Preview Feishu Tasks
```bash
gitlink-cli feishu +task-preview --from-workflow-json report.json --format markdown
```
## 17. Create Feishu Tasks
## 20. Create Feishu Tasks
```bash
gitlink-cli feishu +task-create --from-workflow-json report.json --send --format table
@ -162,7 +239,15 @@ FEISHU_TASK_PROJECT_ID optional
FEISHU_TASK_SECTION_ID optional
```
## 18. Run Go Tests
## 21. Run Screenshot Check
```powershell
.\scripts\feishu-gitlink-screenshot-check.ps1
```
The script lists missing screenshots. It does not fabricate images.
## 22. Run Go Tests
```bash
gofmt -w shortcuts/feishu
@ -172,7 +257,7 @@ go test ./shortcuts
go test ./...
```
## 19. Capture Evidence
## 23. Capture Evidence
Capture terminal logs and screenshots listed in `docs/PR_VISUAL_GUIDE.md`.

View File

@ -1,6 +1,6 @@
# Feishu Smoke Report
# Feishu Smoke Report
Date: 2026-06-26
Date: 2026-06-26 16:29:18 +08:00
## Branch
@ -11,23 +11,17 @@ feat/feishu-export-clean
## Commit
```text
working tree smoke before final implementation commit; base HEAD before this implementation: bcdab0b
9255518304e1a6b0fba9f9e5eee9bdf4f62d8e04
```
## Test Environment
## Mode
```text
Local OS: Windows / PowerShell
Repository: gitlink-cli-feishu-clean
Feishu test enterprise: available only when local environment variables are configured
Real GitLink repo data: public Gitlink/gitlink-cli workflow report generated through gitlink-cli
Previous 3 GitLink PR IDs: not available in current shell; GITLINK_TEST_PR_IDS was not set
preview
```
## Redacted Environment Presence
This section must record presence only, not raw values:
| Variable | Present? |
| --- | --- |
| `FEISHU_WEBHOOK_URL` | missing |
@ -35,104 +29,56 @@ This section must record presence only, not raw values:
| `FEISHU_APP_ID` | missing |
| `FEISHU_APP_SECRET` | missing |
| `FEISHU_WIKI_URL` | missing |
| `FEISHU_WIKI_NODE_TOKEN` | missing |
| `FEISHU_FOLDER_TOKEN` | missing |
| `FEISHU_BASE_APP_TOKEN` | missing |
| `FEISHU_REPORT_TABLE_ID` | missing |
| `FEISHU_ISSUE_TABLE_ID` | missing |
| `FEISHU_PR_TABLE_ID` | missing |
| `FEISHU_CONTRIBUTOR_TABLE_ID` | missing |
| `FEISHU_TASK_TABLE_ID` | missing |
| `FEISHU_TASK_PROJECT_ID` | missing |
| `FEISHU_TASK_SECTION_ID` | missing |
| `GITLINK_OWNER` | missing |
| `GITLINK_REPO` | missing |
| `GITLINK_TEST_PR_IDS` | missing |
| `GITLINK_TOKEN` | missing |
## Commands Run So Far
## Results
```bash
go run . feishu --help
go run . feishu +owner-digest --help
go run . feishu +bitable-sync --help
go run . feishu +task-create --help
go run . feishu +owner-digest --from-workflow-json shortcuts/workflow/testdata/repo_report.json --format table
go run . feishu +contributor-digest --from-workflow-json shortcuts/workflow/testdata/repo_report.json --format table
go run . feishu +bitable-records --from-workflow-json shortcuts/workflow/testdata/repo_report.json --tables reports,issues,prs,contributors,tasks --format table
go run . feishu +bitable-sync --from-workflow-json shortcuts/workflow/testdata/repo_report.json --tables reports,tasks --format table
go run . feishu +task-preview --from-workflow-json shortcuts/workflow/testdata/repo_report.json --format table
$report = Join-Path $env:TEMP 'gitlink-feishu-report-20260626.json'
go run . workflow +repo-report --owner Gitlink --repo gitlink-cli --format json | Set-Content -Encoding utf8 $report
go run . feishu +notify --from-workflow-json $report --format table
go run . feishu +owner-digest --from-workflow-json $report --format table
go run . feishu +contributor-digest --from-workflow-json $report --format table
go run . feishu +bitable-records --from-workflow-json $report --tables reports,issues,prs,contributors,tasks --format table
go run . feishu +task-preview --from-workflow-json $report --format table
go run . feishu +bitable-sync --from-workflow-json $report --tables reports,issues,prs,tasks --format table
go run . feishu +doc-export --from-workflow-json $report --format table
gofmt -w shortcuts/feishu
go test ./shortcuts/feishu
go test ./shortcuts/workflow
go test ./shortcuts
go test ./...
```
## Outputs Summary
| Step | Result | Notes |
| Command | Result | Details |
| --- | --- | --- |
| `feishu --help` | pass | new owner/contributor digest, bitable sync, task preview/create commands visible |
| owner digest preview | pass | role summary generated |
| contributor digest preview | pass | role summary generated |
| bitable records preview | pass | reports/issues/prs/contributors/tasks generated |
| bitable sync preview | pass | preview only, no OpenAPI call |
| task preview | pass | task candidates generated |
| Feishu unit/mock tests | pass | `go test ./shortcuts/feishu` |
| workflow tests | pass | `go test ./shortcuts/workflow` |
| shortcuts tests | pass | `go test ./shortcuts` |
| full repository tests | pass | `go test ./...` |
| public GitLink repo report | pass | `Gitlink/gitlink-cli` report generated in temp directory |
| public GitLink notify preview | pass | preview only, no webhook call |
| public GitLink owner digest | pass | risk/score summary generated |
| public GitLink contributor digest | pass | role-oriented summary generated |
| public GitLink Bitable records | pass | reports/issues/prs/contributors/tasks generated |
| public GitLink Bitable sync preview | pass | preview only, table IDs missing by design |
| public GitLink DocX/Wiki preview | pass | preview only, no Open Platform call |
| feishu help | pass | exit=0 |
| feishu +owner-digest help | pass | exit=0 |
| feishu +contributor-digest help | pass | exit=0 |
| feishu +bitable-sync help | pass | exit=0 |
| feishu +task-preview help | pass | exit=0 |
| feishu +task-create help | pass | exit=0 |
| workflow +repo-report | pass | report=.local/report.json; owner=Gitlink; repo=gitlink-cli |
| notify preview | pass | exit=0 |
| weekly report preview | pass | exit=0 |
| owner digest preview | pass | exit=0 |
| contributor digest preview | pass | exit=0 |
| bitable records preview | pass | exit=0 |
| task preview | pass | exit=0 |
## Real Feishu Webhook Result
## Notes
```text
not executed: FEISHU_WEBHOOK_URL was not present in the current shell.
- No .local/feishu-gitlink.env.ps1 file found. Preview smoke can run with public fallback data; real sends are skipped.
- GITLINK_OWNER/GITLINK_REPO were missing. Preview smoke used public Gitlink/gitlink-cli as a fallback.
## Terminal Log
Local redacted terminal log: `reports/feishu-real-smoke-terminal.log`
This log file is ignored and should not be committed after real runs.
## Screenshot Checklist
Run:
```powershell
.\scripts\feishu-gitlink-screenshot-check.ps1
```
## DocX / Wiki Result
```text
not executed: FEISHU_APP_ID, FEISHU_APP_SECRET, and document target variables were not present in the current shell.
Preview passed with public GitLink report.
```
## Bitable Sync Result
```text
not executed: FEISHU_APP_ID, FEISHU_APP_SECRET, FEISHU_BASE_APP_TOKEN, and table IDs were not present in the current shell.
Preview passed with public GitLink report.
```
## Task Creation Result
```text
not executed: FEISHU_APP_ID and FEISHU_APP_SECRET were not present in the current shell.
Task preview passed with public GitLink report.
```
## Failure Diagnostics
```text
None from local preview, public GitLink read smoke, and unit/mock tests.
Real Open Platform failures must be recorded with endpoint category, HTTP status or Feishu code when available, redacted target type, likely reason, and required permission.
```
## Screenshots Or Terminal Logs
Expected screenshot paths are listed in `docs/PR_VISUAL_GUIDE.md`.
Do not fabricate screenshots.
Do not fabricate screenshots. Capture missing images manually after real Feishu runs.

View File

@ -0,0 +1,88 @@
param(
[ValidateSet("stable", "open-platform", "all")]
[string]$Layer = "stable"
)
$ErrorActionPreference = "Stop"
$RepoRoot = Split-Path -Parent $PSScriptRoot
$LocalEnv = Join-Path $RepoRoot ".local/feishu-gitlink.env.ps1"
function Redact-Value {
param([string]$Value)
if ([string]::IsNullOrWhiteSpace($Value)) { return "" }
if ($Value.Length -le 8) { return "***" }
return "$($Value.Substring(0, 4))...$($Value.Substring($Value.Length - 4))"
}
function Show-Var {
param(
[string]$Name,
[bool]$Sensitive = $true
)
$value = [Environment]::GetEnvironmentVariable($Name)
if ([string]::IsNullOrWhiteSpace($value)) {
Write-Host ("{0}: missing" -f $Name)
return $false
}
if ($Sensitive) {
Write-Host ("{0}: set ({1})" -f $Name, (Redact-Value $value))
} else {
Write-Host ("{0}: set ({1})" -f $Name, $value)
}
return $true
}
function Test-Group {
param(
[string]$Title,
[string[]]$Names,
[string[]]$Optional = @(),
[string[]]$NonSensitive = @()
)
Write-Host ""
Write-Host "[$Title]"
$missingRequired = @()
foreach ($name in $Names) {
$isOptional = $Optional -contains $name
$isSensitive = -not ($NonSensitive -contains $name)
$present = Show-Var -Name $name -Sensitive:$isSensitive
if (-not $present -and -not $isOptional) {
$missingRequired += $name
}
}
return $missingRequired
}
if (Test-Path $LocalEnv) {
. $LocalEnv
Write-Host "Loaded local env: .local/feishu-gitlink.env.ps1"
} else {
Write-Host "No local env file found. Run scripts/feishu-gitlink-setup.ps1 or copy .local/feishu-gitlink.env.example.ps1."
}
$allMissing = @()
if ($Layer -eq "stable" -or $Layer -eq "all") {
$allMissing += Test-Group -Title "Stable webhook" -Names @("FEISHU_WEBHOOK_URL", "FEISHU_WEBHOOK_SECRET") -Optional @("FEISHU_WEBHOOK_SECRET")
}
if ($Layer -eq "open-platform" -or $Layer -eq "all") {
$allMissing += Test-Group -Title "Open Platform app" -Names @("FEISHU_APP_ID", "FEISHU_APP_SECRET")
$allMissing += Test-Group -Title "DocX / Wiki" -Names @("FEISHU_WIKI_URL", "FEISHU_WIKI_NODE_TOKEN", "FEISHU_FOLDER_TOKEN") -Optional @("FEISHU_WIKI_URL", "FEISHU_WIKI_NODE_TOKEN", "FEISHU_FOLDER_TOKEN")
$allMissing += Test-Group -Title "Base / Bitable" -Names @("FEISHU_BASE_APP_TOKEN", "FEISHU_REPORT_TABLE_ID", "FEISHU_ISSUE_TABLE_ID", "FEISHU_PR_TABLE_ID", "FEISHU_CONTRIBUTOR_TABLE_ID", "FEISHU_TASK_TABLE_ID") -Optional @("FEISHU_CONTRIBUTOR_TABLE_ID", "FEISHU_TASK_TABLE_ID")
$allMissing += Test-Group -Title "Feishu Task" -Names @("FEISHU_TASK_PROJECT_ID", "FEISHU_TASK_SECTION_ID") -Optional @("FEISHU_TASK_PROJECT_ID", "FEISHU_TASK_SECTION_ID")
}
if ($Layer -eq "all") {
$allMissing += Test-Group -Title "GitLink test input" -Names @("GITLINK_OWNER", "GITLINK_REPO", "GITLINK_TEST_PR_IDS", "GITLINK_TOKEN") -Optional @("GITLINK_TEST_PR_IDS", "GITLINK_TOKEN") -NonSensitive @("GITLINK_OWNER", "GITLINK_REPO", "GITLINK_TEST_PR_IDS")
}
if ($allMissing.Count -gt 0) {
Write-Host ""
Write-Host "Missing required values for layer '$Layer': $($allMissing -join ', ')"
exit 1
}
Write-Host ""
Write-Host "Layer '$Layer' is ready."
exit 0

View File

@ -0,0 +1,35 @@
$ErrorActionPreference = "Stop"
$RepoRoot = Split-Path -Parent $PSScriptRoot
$Expected = @(
"docs/images/feishu-bot-card.png",
"docs/images/feishu-weekly-report.png",
"docs/images/feishu-owner-digest.png",
"docs/images/feishu-contributor-digest.png",
"docs/images/feishu-bitable-preview.png",
"docs/images/feishu-bitable-sync.png",
"docs/images/feishu-docx-wiki.png",
"docs/images/feishu-task-create.png",
"docs/images/feishu-smoke-terminal.png",
"docs/images/feishu-env-redacted.png"
)
$missing = @()
foreach ($relative in $Expected) {
$path = Join-Path $RepoRoot $relative
if (Test-Path $path) {
Write-Host "Found screenshot: $relative"
} else {
Write-Host "Missing screenshot: $relative"
Write-Host "Open Feishu or terminal and capture this screenshot manually."
$missing += $relative
}
}
if ($missing.Count -gt 0) {
Write-Host ""
Write-Host "Missing screenshots: $($missing.Count)"
exit 1
}
Write-Host "All expected screenshots are present."
exit 0

View File

@ -0,0 +1,199 @@
$ErrorActionPreference = "Stop"
$RepoRoot = Split-Path -Parent $PSScriptRoot
$LocalDir = Join-Path $RepoRoot ".local"
$LocalEnv = Join-Path $LocalDir "feishu-gitlink.env.ps1"
function Redact {
param([string]$Value)
if ([string]::IsNullOrWhiteSpace($Value)) { return "missing" }
if ($Value.Length -le 8) { return "***" }
return "$($Value.Substring(0, 4))...$($Value.Substring($Value.Length - 4))"
}
function Escape-PSString {
param([string]$Value)
if ($null -eq $Value) { return "" }
return $Value.Replace('`', '``').Replace('"', '`"')
}
function SecureString-ToPlainText {
param([System.Security.SecureString]$Secure)
if ($null -eq $Secure) { return "" }
$ptr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($Secure)
try {
return [Runtime.InteropServices.Marshal]::PtrToStringBSTR($ptr)
} finally {
if ($ptr -ne [IntPtr]::Zero) {
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ptr)
}
}
}
function Read-OptionalValue {
param(
[string]$Name,
[string]$CurrentValue,
[string]$Hint
)
$currentLabel = if ([string]::IsNullOrWhiteSpace($CurrentValue)) { "empty" } else { Redact $CurrentValue }
Write-Host ""
Write-Host "$Name ($currentLabel)"
if ($Hint) { Write-Host $Hint }
$value = Read-Host "Enter value, or press Enter to keep current"
if ([string]::IsNullOrWhiteSpace($value)) { return $CurrentValue }
return $value.Trim()
}
function Read-SecretValue {
param(
[string]$Name,
[string]$CurrentValue,
[string]$Hint
)
$currentLabel = if ([string]::IsNullOrWhiteSpace($CurrentValue)) { "empty" } else { "set" }
Write-Host ""
Write-Host "$Name ($currentLabel)"
if ($Hint) { Write-Host $Hint }
$secure = Read-Host "Enter secret, or press Enter to keep current" -AsSecureString
$plain = SecureString-ToPlainText $secure
if ([string]::IsNullOrWhiteSpace($plain)) { return $CurrentValue }
return $plain.Trim()
}
function Open-Url {
param(
[string]$Url,
[string]$Reason
)
Write-Host ""
Write-Host "Opening: $Reason"
Write-Host $Url
Start-Process $Url
}
function Pause-User {
param([string]$Message)
[void](Read-Host $Message)
}
function Env {
param([string]$Name)
return [Environment]::GetEnvironmentVariable($Name)
}
function Missing {
param([string[]]$Names)
foreach ($name in $Names) {
if ([string]::IsNullOrWhiteSpace((Env $name))) { return $true }
}
return $false
}
New-Item -ItemType Directory -Force -Path $LocalDir | Out-Null
if (Test-Path $LocalEnv) {
. $LocalEnv
Write-Host "Loaded existing local env: .local/feishu-gitlink.env.ps1"
}
Write-Host "This setup writes values only to .local/feishu-gitlink.env.ps1."
Write-Host "Do not paste secrets into chat or tracked docs."
if (Missing @("FEISHU_WEBHOOK_URL")) {
Open-Url "https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot" "Feishu custom bot guide"
Pause-User "Add a custom bot to your Feishu test group and copy the webhook URL. Press Enter when ready."
$env:FEISHU_WEBHOOK_URL = Read-OptionalValue "FEISHU_WEBHOOK_URL" (Env "FEISHU_WEBHOOK_URL") "Used by +bot-test, +notify, +weekly-report, +owner-digest, and +contributor-digest."
$env:FEISHU_WEBHOOK_SECRET = Read-SecretValue "FEISHU_WEBHOOK_SECRET" (Env "FEISHU_WEBHOOK_SECRET") "Optional signing secret from the custom bot security settings."
}
if (Missing @("FEISHU_APP_ID", "FEISHU_APP_SECRET")) {
Open-Url "https://open.feishu.cn/app" "Feishu developer console"
Pause-User "Create/open a self-built app, enable required scopes, and copy app_id/app_secret. Press Enter when ready."
$env:FEISHU_APP_ID = Read-OptionalValue "FEISHU_APP_ID" (Env "FEISHU_APP_ID") "Used by experimental +doc-export, +bitable-sync, and +task-create."
$env:FEISHU_APP_SECRET = Read-SecretValue "FEISHU_APP_SECRET" (Env "FEISHU_APP_SECRET") "Self-built app secret."
}
if (Missing @("FEISHU_WIKI_URL", "FEISHU_WIKI_NODE_TOKEN", "FEISHU_FOLDER_TOKEN")) {
Open-Url "https://www.feishu.cn/" "Feishu Docs / Wiki"
Pause-User "Open your target Wiki/Doc page or folder and copy the URL/token. Press Enter when ready."
$env:FEISHU_WIKI_URL = Read-OptionalValue "FEISHU_WIKI_URL" (Env "FEISHU_WIKI_URL") "Existing Wiki or Doc URL for +doc-export."
$env:FEISHU_WIKI_NODE_TOKEN = Read-OptionalValue "FEISHU_WIKI_NODE_TOKEN" (Env "FEISHU_WIKI_NODE_TOKEN") "Optional. Usually parsed from FEISHU_WIKI_URL."
$env:FEISHU_FOLDER_TOKEN = Read-OptionalValue "FEISHU_FOLDER_TOKEN" (Env "FEISHU_FOLDER_TOKEN") "Optional. Used when creating a new DocX in a folder."
}
if (Missing @("FEISHU_BASE_APP_TOKEN", "FEISHU_REPORT_TABLE_ID", "FEISHU_ISSUE_TABLE_ID", "FEISHU_PR_TABLE_ID")) {
Open-Url "https://www.feishu.cn/" "Feishu Base / Bitable"
Pause-User "Open/create the Base for reports, issues, prs, contributors, and tasks. Press Enter when table IDs are ready."
Write-Host "Expected tables: reports, issues, prs, contributors, tasks."
Write-Host "Each selected table should contain at least unique_key and repository."
$env:FEISHU_BASE_APP_TOKEN = Read-OptionalValue "FEISHU_BASE_APP_TOKEN" (Env "FEISHU_BASE_APP_TOKEN") "Base app token."
$env:FEISHU_REPORT_TABLE_ID = Read-OptionalValue "FEISHU_REPORT_TABLE_ID" (Env "FEISHU_REPORT_TABLE_ID") "Reports table ID."
$env:FEISHU_ISSUE_TABLE_ID = Read-OptionalValue "FEISHU_ISSUE_TABLE_ID" (Env "FEISHU_ISSUE_TABLE_ID") "Issues table ID."
$env:FEISHU_PR_TABLE_ID = Read-OptionalValue "FEISHU_PR_TABLE_ID" (Env "FEISHU_PR_TABLE_ID") "Pull request table ID."
$env:FEISHU_CONTRIBUTOR_TABLE_ID = Read-OptionalValue "FEISHU_CONTRIBUTOR_TABLE_ID" (Env "FEISHU_CONTRIBUTOR_TABLE_ID") "Optional contributor table ID."
$env:FEISHU_TASK_TABLE_ID = Read-OptionalValue "FEISHU_TASK_TABLE_ID" (Env "FEISHU_TASK_TABLE_ID") "Optional task candidate table ID."
}
if (Missing @("FEISHU_TASK_PROJECT_ID", "FEISHU_TASK_SECTION_ID")) {
Open-Url "https://www.feishu.cn/" "Feishu Tasks"
Pause-User "Open the Feishu Task project/section if needed. Press Enter when IDs are ready."
$env:FEISHU_TASK_PROJECT_ID = Read-OptionalValue "FEISHU_TASK_PROJECT_ID" (Env "FEISHU_TASK_PROJECT_ID") "Optional. Some Task API paths may not require it."
$env:FEISHU_TASK_SECTION_ID = Read-OptionalValue "FEISHU_TASK_SECTION_ID" (Env "FEISHU_TASK_SECTION_ID") "Optional."
}
if (Missing @("GITLINK_OWNER", "GITLINK_REPO")) {
Open-Url "https://www.gitlink.org.cn/" "GitLink"
Pause-User "Open the target GitLink repository and the previous 3 PRs. Press Enter when ready."
$env:GITLINK_OWNER = Read-OptionalValue "GITLINK_OWNER" (Env "GITLINK_OWNER") "Repository owner, for example Gitlink."
$env:GITLINK_REPO = Read-OptionalValue "GITLINK_REPO" (Env "GITLINK_REPO") "Repository name, for example gitlink-cli."
$env:GITLINK_TEST_PR_IDS = Read-OptionalValue "GITLINK_TEST_PR_IDS" (Env "GITLINK_TEST_PR_IDS") "Comma-separated PR IDs for smoke report references."
$env:GITLINK_TOKEN = Read-SecretValue "GITLINK_TOKEN" (Env "GITLINK_TOKEN") "Optional if gitlink-cli is already logged in."
}
if (-not [string]::IsNullOrWhiteSpace($env:GITLINK_OWNER) -and -not [string]::IsNullOrWhiteSpace($env:GITLINK_REPO)) {
Open-Url "https://www.gitlink.org.cn/$env:GITLINK_OWNER/$env:GITLINK_REPO" "Target GitLink repository"
if (-not [string]::IsNullOrWhiteSpace($env:GITLINK_TEST_PR_IDS)) {
Write-Host "GITLINK_TEST_PR_IDS set: $env:GITLINK_TEST_PR_IDS"
Write-Host "Verify the PR page URL pattern manually if needed."
}
}
$vars = @(
"FEISHU_WEBHOOK_URL",
"FEISHU_WEBHOOK_SECRET",
"FEISHU_APP_ID",
"FEISHU_APP_SECRET",
"FEISHU_WIKI_URL",
"FEISHU_WIKI_NODE_TOKEN",
"FEISHU_FOLDER_TOKEN",
"FEISHU_BASE_APP_TOKEN",
"FEISHU_REPORT_TABLE_ID",
"FEISHU_ISSUE_TABLE_ID",
"FEISHU_PR_TABLE_ID",
"FEISHU_CONTRIBUTOR_TABLE_ID",
"FEISHU_TASK_TABLE_ID",
"FEISHU_TASK_PROJECT_ID",
"FEISHU_TASK_SECTION_ID",
"GITLINK_OWNER",
"GITLINK_REPO",
"GITLINK_TEST_PR_IDS",
"GITLINK_TOKEN"
)
$lines = @(
"# Local Feishu/GitLink smoke-test environment.",
"# Generated by scripts/feishu-gitlink-setup.ps1.",
"# Do not commit this file."
)
foreach ($name in $vars) {
$value = [Environment]::GetEnvironmentVariable($name)
$lines += ('$' + ('env:{0}="{1}"' -f $name, (Escape-PSString $value)))
}
$lines | Set-Content -LiteralPath $LocalEnv -Encoding utf8
Write-Host ""
Write-Host "Saved local env: .local/feishu-gitlink.env.ps1"
Write-Host "Redacted summary:"
foreach ($name in $vars) {
$value = [Environment]::GetEnvironmentVariable($name)
Write-Host ("{0}: {1}" -f $name, (Redact $value))
}

View File

@ -0,0 +1,336 @@
param(
[ValidateSet("preview", "stable", "open-platform", "all")]
[string]$Mode = "preview"
)
$ErrorActionPreference = "Stop"
$RepoRoot = Split-Path -Parent $PSScriptRoot
$LocalDir = Join-Path $RepoRoot ".local"
$LocalEnv = Join-Path $LocalDir "feishu-gitlink.env.ps1"
$ReportJSON = Join-Path $LocalDir "report.json"
$TerminalLog = Join-Path $RepoRoot "reports/feishu-real-smoke-terminal.log"
$DateStamp = Get-Date -Format "yyyyMMdd"
$SmokeReport = Join-Path $RepoRoot "reports/FEISHU_SMOKE_$DateStamp.md"
$Results = New-Object System.Collections.Generic.List[object]
$Notes = New-Object System.Collections.Generic.List[string]
function Env {
param([string]$Name)
return [Environment]::GetEnvironmentVariable($Name)
}
function Has-Env {
param([string[]]$Names)
foreach ($name in $Names) {
if ([string]::IsNullOrWhiteSpace((Env $name))) { return $false }
}
return $true
}
function Redact-Value {
param([string]$Value)
if ([string]::IsNullOrWhiteSpace($Value)) { return "" }
if ($Value.Length -le 8) { return "***" }
return "$($Value.Substring(0, 4))...$($Value.Substring($Value.Length - 4))"
}
function Redact-Text {
param([string]$Text)
if ($null -eq $Text) { return "" }
$redacted = $Text
$names = @(
"FEISHU_WEBHOOK_URL",
"FEISHU_WEBHOOK_SECRET",
"FEISHU_APP_ID",
"FEISHU_APP_SECRET",
"FEISHU_WIKI_URL",
"FEISHU_WIKI_NODE_TOKEN",
"FEISHU_FOLDER_TOKEN",
"FEISHU_BASE_APP_TOKEN",
"FEISHU_REPORT_TABLE_ID",
"FEISHU_ISSUE_TABLE_ID",
"FEISHU_PR_TABLE_ID",
"FEISHU_CONTRIBUTOR_TABLE_ID",
"FEISHU_TASK_TABLE_ID",
"FEISHU_TASK_PROJECT_ID",
"FEISHU_TASK_SECTION_ID",
"GITLINK_TOKEN"
)
foreach ($name in $names) {
$value = Env $name
if (-not [string]::IsNullOrWhiteSpace($value)) {
$redacted = [regex]::Replace($redacted, [regex]::Escape($value), (Redact-Value $value))
}
}
$redacted = [regex]::Replace($redacted, 'tenant_access_token"\s*:\s*"[^"]+', 'tenant_access_token":"REDACTED')
return $redacted
}
function Add-Result {
param(
[string]$Name,
[string]$Status,
[string]$Details
)
$Results.Add([pscustomobject]@{
Name = $Name
Status = $Status
Details = (Redact-Text $Details)
}) | Out-Null
}
function Write-Log {
param([string]$Text)
Add-Content -LiteralPath $TerminalLog -Value (Redact-Text $Text)
}
function Invoke-Cmd {
param(
[string]$Name,
[string[]]$CommandArgs,
[bool]$Required = $false
)
$display = $CommandArgs -join " "
Write-Host "RUN: $Name"
Write-Log ""
Write-Log "## $Name"
Write-Log "COMMAND: $display"
$exe = $CommandArgs[0]
$rest = @()
if ($CommandArgs.Count -gt 1) {
$rest = $CommandArgs[1..($CommandArgs.Count - 1)]
}
$output = & $exe @rest 2>&1 | Out-String
$exit = $LASTEXITCODE
Write-Log $output
if ($exit -eq 0) {
Add-Result $Name "pass" "exit=0"
} else {
Add-Result $Name "fail" ("exit={0}; {1}" -f $exit, (($output -split "`r?`n" | Select-Object -First 4) -join " "))
if ($Required) {
throw "$Name failed with exit $exit"
}
}
}
function Invoke-ReportGeneration {
$owner = Env "GITLINK_OWNER"
$repo = Env "GITLINK_REPO"
if ([string]::IsNullOrWhiteSpace($owner) -or [string]::IsNullOrWhiteSpace($repo)) {
$owner = "Gitlink"
$repo = "gitlink-cli"
$Notes.Add("GITLINK_OWNER/GITLINK_REPO were missing. Preview smoke used public Gitlink/gitlink-cli as a fallback.") | Out-Null
}
if (-not [string]::IsNullOrWhiteSpace((Env "GITLINK_TEST_PR_IDS"))) {
$Notes.Add("GITLINK_TEST_PR_IDS was provided, but workflow +repo-report currently does not support explicit PR ID filtering. The smoke test used the real repository report instead.") | Out-Null
}
Write-Host "Generating workflow report for $owner/$repo"
Write-Log ""
Write-Log "## Generate workflow report"
$output = & go run . workflow +repo-report --owner $owner --repo $repo --format json 2>&1 | Out-String
$exit = $LASTEXITCODE
if ($exit -ne 0) {
Write-Log $output
Add-Result "workflow +repo-report" "fail" ("exit={0}; {1}" -f $exit, (($output -split "`r?`n" | Select-Object -First 4) -join " "))
throw "workflow +repo-report failed"
}
$output | Set-Content -LiteralPath $ReportJSON -Encoding utf8
Write-Log "Report written to .local/report.json"
Add-Result "workflow +repo-report" "pass" "report=.local/report.json; owner=$owner; repo=$repo"
}
function Add-Skip {
param([string]$Name, [string]$Reason)
Write-Host "SKIP: $Name - $Reason"
Write-Log ""
Write-Log "## $Name"
Write-Log "SKIP: $Reason"
Add-Result $Name "skip" $Reason
}
function Env-Status {
param([string]$Name)
if ([string]::IsNullOrWhiteSpace((Env $Name))) { return "missing" }
return "present"
}
function Escape-Table {
param([string]$Value)
if ($null -eq $Value) { return "" }
return ($Value -replace '\|', '\|' -replace "`r?`n", " ")
}
function Write-SmokeReport {
$branch = (git branch --show-current | Out-String).Trim()
$commit = (git rev-parse HEAD | Out-String).Trim()
$envNames = @(
"FEISHU_WEBHOOK_URL",
"FEISHU_WEBHOOK_SECRET",
"FEISHU_APP_ID",
"FEISHU_APP_SECRET",
"FEISHU_WIKI_URL",
"FEISHU_WIKI_NODE_TOKEN",
"FEISHU_FOLDER_TOKEN",
"FEISHU_BASE_APP_TOKEN",
"FEISHU_REPORT_TABLE_ID",
"FEISHU_ISSUE_TABLE_ID",
"FEISHU_PR_TABLE_ID",
"FEISHU_CONTRIBUTOR_TABLE_ID",
"FEISHU_TASK_TABLE_ID",
"FEISHU_TASK_PROJECT_ID",
"FEISHU_TASK_SECTION_ID",
"GITLINK_OWNER",
"GITLINK_REPO",
"GITLINK_TEST_PR_IDS",
"GITLINK_TOKEN"
)
$lines = @(
"# Feishu Smoke Report",
"",
"Date: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss zzz')",
"",
"## Branch",
"",
'```text',
$branch,
'```',
"",
"## Commit",
"",
'```text',
$commit,
'```',
"",
"## Mode",
"",
'```text',
$Mode,
'```',
"",
"## Redacted Environment Presence",
"",
"| Variable | Present? |",
"| --- | --- |"
)
foreach ($name in $envNames) {
$lines += ('| `{0}` | {1} |' -f $name, (Env-Status $name))
}
$lines += @(
"",
"## Results",
"",
"| Command | Result | Details |",
"| --- | --- | --- |"
)
foreach ($result in $Results) {
$lines += "| $(Escape-Table $result.Name) | $(Escape-Table $result.Status) | $(Escape-Table $result.Details) |"
}
$lines += @(
"",
"## Notes",
""
)
if ($Notes.Count -eq 0) {
$lines += "- None."
} else {
foreach ($note in $Notes) {
$lines += "- $(Redact-Text $note)"
}
}
$lines += @(
"",
"## Terminal Log",
"",
'Local redacted terminal log: `reports/feishu-real-smoke-terminal.log`',
"",
"This log file is ignored and should not be committed after real runs.",
"",
"## Screenshot Checklist",
"",
"Run:",
"",
'```powershell',
".\scripts\feishu-gitlink-screenshot-check.ps1",
'```',
"",
"Do not fabricate screenshots. Capture missing images manually after real Feishu runs."
)
$lines | Set-Content -LiteralPath $SmokeReport -Encoding utf8
Write-Host "Smoke report written: $SmokeReport"
}
Push-Location $RepoRoot
try {
New-Item -ItemType Directory -Force -Path $LocalDir | Out-Null
New-Item -ItemType Directory -Force -Path (Join-Path $RepoRoot "reports") | Out-Null
if (Test-Path $LocalEnv) {
. $LocalEnv
Write-Host "Loaded local env: .local/feishu-gitlink.env.ps1"
} else {
$Notes.Add("No .local/feishu-gitlink.env.ps1 file found. Preview smoke can run with public fallback data; real sends are skipped.") | Out-Null
}
"# Feishu/GitLink smoke terminal log $(Get-Date -Format o)" | Set-Content -LiteralPath $TerminalLog -Encoding utf8
$helpCommands = @(
"+owner-digest",
"+contributor-digest",
"+bitable-sync",
"+task-preview",
"+task-create"
)
Invoke-Cmd "feishu help" @("go", "run", ".", "feishu", "--help") $true
foreach ($command in $helpCommands) {
Invoke-Cmd "feishu $command help" @("go", "run", ".", "feishu", $command, "--help") $true
}
Invoke-ReportGeneration
$runPreview = $Mode -in @("preview", "stable", "open-platform", "all")
$runStable = $Mode -in @("stable", "all")
$runOpenPlatform = $Mode -in @("open-platform", "all")
if ($runPreview) {
Invoke-Cmd "notify preview" @("go", "run", ".", "feishu", "+notify", "--from-workflow-json", $ReportJSON, "--format", "table") $true
Invoke-Cmd "weekly report preview" @("go", "run", ".", "feishu", "+weekly-report", "--from-workflow-json", $ReportJSON, "--format", "markdown") $true
Invoke-Cmd "owner digest preview" @("go", "run", ".", "feishu", "+owner-digest", "--from-workflow-json", $ReportJSON, "--format", "table") $true
Invoke-Cmd "contributor digest preview" @("go", "run", ".", "feishu", "+contributor-digest", "--from-workflow-json", $ReportJSON, "--format", "table") $true
Invoke-Cmd "bitable records preview" @("go", "run", ".", "feishu", "+bitable-records", "--from-workflow-json", $ReportJSON, "--tables", "reports,issues,prs,contributors,tasks", "--format", "table") $true
Invoke-Cmd "task preview" @("go", "run", ".", "feishu", "+task-preview", "--from-workflow-json", $ReportJSON, "--format", "table") $true
}
if ($runStable) {
if (Has-Env @("FEISHU_WEBHOOK_URL")) {
Invoke-Cmd "bot-test send" @("go", "run", ".", "feishu", "+bot-test", "--send", "--format", "table") $false
Invoke-Cmd "notify send" @("go", "run", ".", "feishu", "+notify", "--from-workflow-json", $ReportJSON, "--send", "--format", "table") $false
Invoke-Cmd "weekly report send" @("go", "run", ".", "feishu", "+weekly-report", "--from-workflow-json", $ReportJSON, "--send", "--format", "table") $false
Invoke-Cmd "owner digest send" @("go", "run", ".", "feishu", "+owner-digest", "--from-workflow-json", $ReportJSON, "--send", "--format", "table") $false
Invoke-Cmd "contributor digest send" @("go", "run", ".", "feishu", "+contributor-digest", "--from-workflow-json", $ReportJSON, "--send", "--format", "table") $false
} else {
Add-Skip "stable webhook send" "FEISHU_WEBHOOK_URL missing"
}
}
if ($runOpenPlatform) {
if (Has-Env @("FEISHU_APP_ID", "FEISHU_APP_SECRET") -and (Has-Env @("FEISHU_WIKI_URL") -or Has-Env @("FEISHU_WIKI_NODE_TOKEN") -or Has-Env @("FEISHU_FOLDER_TOKEN"))) {
Invoke-Cmd "doc-export send" @("go", "run", ".", "feishu", "+doc-export", "--from-workflow-json", $ReportJSON, "--send", "--format", "table") $false
} else {
Add-Skip "doc-export send" "missing FEISHU_APP_ID/FEISHU_APP_SECRET or DocX/Wiki target"
}
if (Has-Env @("FEISHU_APP_ID", "FEISHU_APP_SECRET", "FEISHU_BASE_APP_TOKEN", "FEISHU_REPORT_TABLE_ID", "FEISHU_ISSUE_TABLE_ID", "FEISHU_PR_TABLE_ID")) {
Invoke-Cmd "bitable-sync send" @("go", "run", ".", "feishu", "+bitable-sync", "--from-workflow-json", $ReportJSON, "--tables", "reports,issues,prs,contributors,tasks", "--send", "--format", "table") $false
} else {
Add-Skip "bitable-sync send" "missing app/base/table variables"
}
if (Has-Env @("FEISHU_APP_ID", "FEISHU_APP_SECRET")) {
Invoke-Cmd "task-create send" @("go", "run", ".", "feishu", "+task-create", "--from-workflow-json", $ReportJSON, "--send", "--format", "table") $false
} else {
Add-Skip "task-create send" "missing FEISHU_APP_ID/FEISHU_APP_SECRET"
}
}
Write-SmokeReport
} finally {
Pop-Location
}