mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-25 23:35:00 +03:00
ci: run the Postgres suites that never ran and lint the shell (#6917)
Eight Postgres-only tests skipped everywhere because no workflow named them. The durable-first job now runs the service and outbound ones, and a scratch-database job runs the migration tests that drop their own tables. Every Postgres step counts top-level passes, so a renamed test cannot leave a step green. A shell-lint job runs actionlint on the workflows and shellcheck on the shipped scripts (the legacy installers at error severity), golangci-lint is pinned, and both the codegen job and make verify fail when docs/public/openapi.json drifts from the panel's spec; docs-ci also fails on a stale API reference. npm audit moves to its own script outside make verify, since it depends on the network.
This commit is contained in:
@@ -71,24 +71,34 @@ jobs:
|
|||||||
- name: PostgreSQL durable-first tests
|
- name: PostgreSQL durable-first tests
|
||||||
run: |
|
run: |
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
go test ./internal/web/service -run 'PostgresCommitFailure' -count=1 -v | tee /tmp/postgres-durable-first.log
|
go test ./internal/web/service -run 'PostgresCommitFailure|^TestSyncInboundClientsOnPostgres$|^TestSetClientLimitHwidIsSerializedWithSyncInbound$' -count=1 -v | tee /tmp/postgres-durable-first.log
|
||||||
# Count passes rather than assert no SKIP: a renamed or deleted test
|
# Count passes rather than assert no SKIP: a renamed or deleted test
|
||||||
# prints "no tests to run" and exits 0, leaving the step green for nothing.
|
# prints "no tests to run" and exits 0, leaving the step green for nothing.
|
||||||
passed=$(grep -c -- '--- PASS' /tmp/postgres-durable-first.log || true)
|
passed=$(grep -c -- '^--- PASS' /tmp/postgres-durable-first.log || true)
|
||||||
|
if [ "$passed" -lt 4 ]; then
|
||||||
|
echo "expected at least 4 passing durable-first tests, got $passed" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: PostgreSQL deferred-commit test
|
||||||
|
run: |
|
||||||
|
set -o pipefail
|
||||||
|
go test ./internal/web/service/outbound -run '^TestAddTrafficReturnsDeferredCommitFailure$' -count=1 -v | tee /tmp/postgres-outbound.log
|
||||||
|
passed=$(grep -c -- '^--- PASS' /tmp/postgres-outbound.log || true)
|
||||||
if [ "$passed" -lt 1 ]; then
|
if [ "$passed" -lt 1 ]; then
|
||||||
echo "expected at least 1 passing durable-first test, got $passed" >&2
|
echo "expected at least 1 passing deferred-commit test, got $passed" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: PostgreSQL schema and migration tests
|
- name: PostgreSQL schema and migration tests
|
||||||
run: |
|
run: |
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
go test ./internal/database -run '^(TestHostAutoMigrateCreatesColumns_Postgres|TestMigrate_Postgres|TestClientWeeklyRenewMigration_Postgres|TestMigrate_LegacyBooleanVerifyPeerCertByNameIsBlanked_Postgres|TestClientKeepAlivePostgresMigrationPreservesLegacyScalars)$' -count=1 -v | tee /tmp/postgres-schema.log
|
go test ./internal/database -run '^(TestHostAutoMigrateCreatesColumns_Postgres|TestMigrate_Postgres|TestClientWeeklyRenewMigration_Postgres|TestMigrate_LegacyBooleanVerifyPeerCertByNameIsBlanked_Postgres|TestClientKeepAlivePostgresMigrationPreservesLegacyScalars|TestPostgresModelSettled_TracksSchemaPresence)$' -count=1 -v | tee /tmp/postgres-schema.log
|
||||||
# All must pass. Counting, not SKIP-matching: renaming a test would
|
# All must pass. Counting, not SKIP-matching: renaming a test would
|
||||||
# otherwise leave this step green while testing nothing.
|
# otherwise leave this step green while testing nothing.
|
||||||
passed=$(grep -c -- '^--- PASS' /tmp/postgres-schema.log || true)
|
passed=$(grep -c -- '^--- PASS' /tmp/postgres-schema.log || true)
|
||||||
if [ "$passed" -lt 5 ]; then
|
if [ "$passed" -lt 6 ]; then
|
||||||
echo "expected at least 5 passing PostgreSQL schema tests, got $passed" >&2
|
echo "expected at least 6 passing PostgreSQL schema tests, got $passed" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -112,6 +122,46 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# The migration tests write and drop their own tables, so they need a
|
||||||
|
# scratch database instead of the durable one the panel tests use.
|
||||||
|
postgres-migration:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:16
|
||||||
|
env:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
POSTGRES_DB: xui_scratch
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
options: >-
|
||||||
|
--health-cmd "pg_isready -U postgres -d xui_scratch"
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
env:
|
||||||
|
XUI_TEST_PG_DSN: "host=127.0.0.1 port=5432 user=postgres password=postgres dbname=xui_scratch sslmode=disable"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
- uses: actions/setup-go@v7
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
cache: true
|
||||||
|
- name: Stub internal/web/dist for go:embed
|
||||||
|
run: mkdir -p internal/web/dist && touch internal/web/dist/.gitkeep
|
||||||
|
- name: PostgreSQL migration tests
|
||||||
|
run: |
|
||||||
|
set -o pipefail
|
||||||
|
go test ./internal/database -run '^(TestMigrateData_CompositeKeyTableLargerThanBatch|TestMigrateData_PreservesFalseDefaultedColumns|TestMigrateData_FailedCopyLeavesDestinationUntouched|TestClientHwidSchemaPostgres)$' -count=1 -v | tee /tmp/postgres-migration.log
|
||||||
|
# Count passes rather than assert no SKIP: a renamed or deleted test
|
||||||
|
# prints "no tests to run" and exits 0, leaving the step green for nothing.
|
||||||
|
passed=$(grep -c -- '^--- PASS' /tmp/postgres-migration.log || true)
|
||||||
|
if [ "$passed" -lt 4 ]; then
|
||||||
|
echo "expected at least 4 passing PostgreSQL migration tests, got $passed" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
codegen:
|
codegen:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -128,6 +178,8 @@ jobs:
|
|||||||
working-directory: frontend
|
working-directory: frontend
|
||||||
- name: Fail if generated files are stale (run 'npm run gen' and commit)
|
- name: Fail if generated files are stale (run 'npm run gen' and commit)
|
||||||
run: git diff --exit-code -- frontend/src/generated frontend/public/openapi.json
|
run: git diff --exit-code -- frontend/src/generated frontend/public/openapi.json
|
||||||
|
- name: Fail if the docs spec is not the panel's
|
||||||
|
run: cmp frontend/public/openapi.json docs/public/openapi.json
|
||||||
|
|
||||||
govulncheck:
|
govulncheck:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -207,7 +259,33 @@ jobs:
|
|||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v9
|
uses: golangci/golangci-lint-action@v9
|
||||||
with:
|
with:
|
||||||
version: latest
|
# Pinned: "latest" let the lint result drift between runs, so the same
|
||||||
|
# commit could pass today and fail tomorrow on a new upstream release.
|
||||||
|
version: v2.15.0
|
||||||
|
|
||||||
|
# Lint the shell scripts and the workflows themselves. The three legacy
|
||||||
|
# scripts carry a warning backlog (unquoted expansions, masked return codes)
|
||||||
|
# and are gated on errors only; the scripts that are clean today are gated on
|
||||||
|
# everything.
|
||||||
|
shell-lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
- uses: actions/setup-go@v7
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
cache: true
|
||||||
|
- name: actionlint
|
||||||
|
# The shellcheck integration is off: it reports the stylistic rules
|
||||||
|
# (SC2086, SC2046) that main's own workflows trip. The shell scripts
|
||||||
|
# the panel ships are linted by the shellcheck step below.
|
||||||
|
run: go run github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 -color -shellcheck=
|
||||||
|
- name: Install shellcheck
|
||||||
|
run: sudo apt-get update && sudo apt-get install -y shellcheck
|
||||||
|
- name: shellcheck
|
||||||
|
run: |
|
||||||
|
shellcheck --severity=style DockerInit.sh DockerEntrypoint.sh deploy/test/smoke-noninteractive.sh
|
||||||
|
shellcheck --severity=error install.sh update.sh x-ui.sh
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -243,5 +321,5 @@ jobs:
|
|||||||
run: npm run build-storybook
|
run: npm run build-storybook
|
||||||
working-directory: frontend
|
working-directory: frontend
|
||||||
- name: Audit
|
- name: Audit
|
||||||
run: npm audit --omit=dev --audit-level=high
|
run: npm run audit
|
||||||
working-directory: frontend
|
working-directory: frontend
|
||||||
|
|||||||
@@ -5,10 +5,14 @@ on:
|
|||||||
branches: [main]
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
- 'docs/**'
|
- 'docs/**'
|
||||||
|
- 'frontend/public/openapi.json'
|
||||||
|
- 'frontend/src/pages/api-docs/endpoints.ts'
|
||||||
- '.github/workflows/docs-ci.yml'
|
- '.github/workflows/docs-ci.yml'
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- 'docs/**'
|
- 'docs/**'
|
||||||
|
- 'frontend/public/openapi.json'
|
||||||
|
- 'frontend/src/pages/api-docs/endpoints.ts'
|
||||||
- '.github/workflows/docs-ci.yml'
|
- '.github/workflows/docs-ci.yml'
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
@@ -48,5 +52,18 @@ jobs:
|
|||||||
- name: Test
|
- name: Test
|
||||||
run: pnpm test
|
run: pnpm test
|
||||||
|
|
||||||
|
# The API reference is generated from the panel's own spec. The copy into
|
||||||
|
# docs/public and the generated pages both have to be in step with it.
|
||||||
|
- name: The docs spec must be the panel's
|
||||||
|
run: cmp frontend/public/openapi.json docs/public/openapi.json
|
||||||
|
working-directory: .
|
||||||
|
|
||||||
|
- name: Regenerate the API reference
|
||||||
|
run: pnpm gen:api
|
||||||
|
|
||||||
|
- name: Fail if the generated API reference is stale
|
||||||
|
run: git diff --exit-code -- docs/content/docs/en/reference/api
|
||||||
|
working-directory: .
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: pnpm build
|
run: pnpm build
|
||||||
|
|||||||
@@ -102,10 +102,11 @@ file locations when it can answer in one hop.
|
|||||||
never hand-write them. A new struct must be added to openapigen's `StructAllow`
|
never hand-write them. A new struct must be added to openapigen's `StructAllow`
|
||||||
allowlist (`tools/openapigen/main.go`) or it is silently omitted from
|
allowlist (`tools/openapigen/main.go`) or it is silently omitted from
|
||||||
schemas/examples (and `build-openapi.mjs` then fails on the missing schema).
|
schemas/examples (and `build-openapi.mjs` then fails on the missing schema).
|
||||||
- A new or renamed endpoint has a FOURTH step nothing checks: copy
|
- A new or renamed endpoint has a FOURTH step: copy
|
||||||
`frontend/public/openapi.json` → `docs/public/openapi.json`, then
|
`frontend/public/openapi.json` → `docs/public/openapi.json`, then
|
||||||
`cd docs && pnpm gen:api` to refresh the MDX under
|
`cd docs && pnpm gen:api` to refresh the MDX under
|
||||||
`docs/content/docs/en/reference/api/`. `docs-ci.yml` fires only on `docs/**`.
|
`docs/content/docs/en/reference/api/`. The `codegen` job and `make verify`
|
||||||
|
fail on a stale copy, and `docs-ci.yml` fails on stale MDX.
|
||||||
- A new English i18n key goes in EVERY locale JSON in `internal/web/translation/`
|
- A new English i18n key goes in EVERY locale JSON in `internal/web/translation/`
|
||||||
(14 files) AND must be referenced from `frontend/src` or Go in the SAME commit —
|
(14 files) AND must be referenced from `frontend/src` or Go in the SAME commit —
|
||||||
`frontend/src/test/i18n-dead-keys.test.ts` fails both ways. It is a frontend
|
`frontend/src/test/i18n-dead-keys.test.ts` fails both ways. It is a frontend
|
||||||
@@ -189,7 +190,7 @@ reads as a broken repo, not a missing step. Run `make dist-stub` once; every
|
|||||||
`make` Go target already depends on it, which is why `make test-go` beats
|
`make` Go target already depends on it, which is why `make test-go` beats
|
||||||
`go test ./...`. Run `make help` for all targets. The local gate:
|
`go test ./...`. Run `make help` for all targets. The local gate:
|
||||||
|
|
||||||
make verify # gen-check + lint + typecheck + test + build + build-storybook
|
make verify # gen-check + lint + typecheck + test + build + build-storybook + docs-openapi-check
|
||||||
|
|
||||||
That is the *fast* gate, not all of CI. `ci.yml` also runs `make race`,
|
That is the *fast* gate, not all of CI. `ci.yml` also runs `make race`,
|
||||||
`make vulncheck`, a live-Postgres job (where a SKIP counts as a failure),
|
`make vulncheck`, a live-Postgres job (where a SKIP counts as a failure),
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ lint-go: dist-stub ## golangci-lint on Go sources
|
|||||||
lint-fe: ## oxlint on frontend sources
|
lint-fe: ## oxlint on frontend sources
|
||||||
cd $(FRONTEND) && npm run lint
|
cd $(FRONTEND) && npm run lint
|
||||||
|
|
||||||
|
.PHONY: audit-fe
|
||||||
|
audit-fe: ## Audit the frontend's production dependencies (needs the network)
|
||||||
|
cd $(FRONTEND) && npm run audit
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint: lint-go lint-fe ## All linters
|
lint: lint-go lint-fe ## All linters
|
||||||
|
|
||||||
@@ -72,6 +76,10 @@ test: test-go test-fe ## All tests
|
|||||||
vulncheck: dist-stub ## govulncheck
|
vulncheck: dist-stub ## govulncheck
|
||||||
go run golang.org/x/vuln/cmd/govulncheck@latest ./...
|
go run golang.org/x/vuln/cmd/govulncheck@latest ./...
|
||||||
|
|
||||||
|
.PHONY: docs-openapi-check
|
||||||
|
docs-openapi-check: ## Fail if the docs OpenAPI spec is not the panel's
|
||||||
|
cmp frontend/public/openapi.json docs/public/openapi.json
|
||||||
|
|
||||||
.PHONY: build-fe
|
.PHONY: build-fe
|
||||||
build-fe: ## Build the Vite bundles into internal/web/dist
|
build-fe: ## Build the Vite bundles into internal/web/dist
|
||||||
cd $(FRONTEND) && npm run build
|
cd $(FRONTEND) && npm run build
|
||||||
@@ -85,7 +93,10 @@ build-storybook: ## Build the static Storybook (compile-checks all stories)
|
|||||||
cd $(FRONTEND) && npm run build-storybook
|
cd $(FRONTEND) && npm run build-storybook
|
||||||
|
|
||||||
# The PR gate. Matches ci.yml: codegen freshness, both linters, the formatter,
|
# The PR gate. Matches ci.yml: codegen freshness, both linters, the formatter,
|
||||||
# typecheck, both test suites, a full build, and the Storybook compile-check.
|
# typecheck, both test suites, a full build, the Storybook compile-check and the
|
||||||
|
# docs spec check. The dependency audit is a separate target: it needs the
|
||||||
|
# network and a live advisory database, so an unchanged commit could pass today
|
||||||
|
# and fail tomorrow.
|
||||||
.PHONY: verify
|
.PHONY: verify
|
||||||
verify: gen-check lint format-check typecheck test build build-storybook ## Full local gate (mirrors CI)
|
verify: gen-check lint format-check typecheck test build build-storybook docs-openapi-check ## Full local gate (mirrors CI)
|
||||||
@echo "verify: OK"
|
@echo "verify: OK"
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
"build-storybook": "storybook build",
|
"build-storybook": "storybook build",
|
||||||
"gen": "npm run gen:zod && npm run gen:api",
|
"gen": "npm run gen:zod && npm run gen:api",
|
||||||
"gen:api": "node scripts/build-openapi.mjs",
|
"gen:api": "node scripts/build-openapi.mjs",
|
||||||
|
"audit": "npm audit --omit=dev --audit-level=high",
|
||||||
"gen:zod": "cd .. && go run ./tools/openapigen",
|
"gen:zod": "cd .. && go run ./tools/openapigen",
|
||||||
"prepare": "cd .. && husky frontend/.husky || true"
|
"prepare": "cd .. && husky frontend/.husky || true"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ iplimit_log_path="${log_folder}/3xipl.log"
|
|||||||
iplimit_banned_log_path="${log_folder}/3xipl-banned.log"
|
iplimit_banned_log_path="${log_folder}/3xipl-banned.log"
|
||||||
|
|
||||||
confirm() {
|
confirm() {
|
||||||
if [[ $# > 1 ]]; then
|
if [[ $# -gt 1 ]]; then
|
||||||
echo && read -rp "$1 [Default $2]: " temp
|
echo && read -rp "$1 [Default $2]: " temp
|
||||||
if [[ "${temp}" == "" ]]; then
|
if [[ "${temp}" == "" ]]; then
|
||||||
temp=$2
|
temp=$2
|
||||||
@@ -3639,7 +3639,7 @@ show_menu() {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ $# > 0 ]]; then
|
if [[ $# -gt 0 ]]; then
|
||||||
case $1 in
|
case $1 in
|
||||||
"start")
|
"start")
|
||||||
check_install 0 && start 0
|
check_install 0 && start 0
|
||||||
|
|||||||
Reference in New Issue
Block a user