Add opt-in shell lint and format targets
Introduce make lint-shell / fmt-shell with shellcheck and shfmt (-i 2 -ci -bn), normalize first-party scripts, and document the new targets. Keep them out of make lint / fmt. Signed-off-by: Ludovic Vielle <ludovic@probo.com>
This commit is contained in:
41
.github/workflows/make.yaml
vendored
41
.github/workflows/make.yaml
vendored
@@ -281,12 +281,6 @@ jobs:
|
||||
- uses: "./.github/actions/setup"
|
||||
with:
|
||||
node: "false"
|
||||
- name: "Lint install.sh"
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq shellcheck
|
||||
sh -n cmd/probo-agent/installer/install.sh
|
||||
shellcheck cmd/probo-agent/installer/install.sh
|
||||
- name: "Build probo-agent"
|
||||
env:
|
||||
CGO_ENABLED: "0"
|
||||
@@ -396,8 +390,12 @@ jobs:
|
||||
swift-version: "6.0"
|
||||
- uses: reviewdog/action-setup@d8a7baabd7f3e8544ee4dbde3ee41d0011c3a93f # v1.5.0
|
||||
- name: "Install SwiftLint"
|
||||
env:
|
||||
SWIFTLINT_VERSION: "0.65.0"
|
||||
SWIFTLINT_SHA256: "79306a34e5c7cc55a220cd108cbb861dcad5f10138dcdf261e2624ae8b0a486b"
|
||||
run: |
|
||||
curl -sL "https://github.com/realm/SwiftLint/releases/download/0.65.0/swiftlint_linux_amd64.zip" -o /tmp/swiftlint.zip
|
||||
curl -sL "https://github.com/realm/SwiftLint/releases/download/${SWIFTLINT_VERSION}/swiftlint_linux_amd64.zip" -o /tmp/swiftlint.zip
|
||||
echo "${SWIFTLINT_SHA256} /tmp/swiftlint.zip" | sha256sum -c -
|
||||
sudo unzip -o /tmp/swiftlint.zip -d /usr/local/bin
|
||||
sudo chmod +x /usr/local/bin/swiftlint
|
||||
swiftlint version
|
||||
@@ -415,6 +413,35 @@ jobs:
|
||||
swiftlint lint --config .swiftlint.yml --cache-path /tmp/swiftlint-cache 2>&1 | \
|
||||
reviewdog -f=swiftlint -reporter=github-pr-review -filter-mode=nofilter -name="swiftlint" || true
|
||||
|
||||
lint-shell:
|
||||
name: "lint-shell"
|
||||
runs-on: "runs-on=${{ github.run_id }}/runner=2cpu-linux-x64/extras=s3-cache"
|
||||
permissions:
|
||||
contents: "read"
|
||||
steps:
|
||||
- uses: "actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0" # v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: "runs-on/action@d141ef83eb66d096ce8afc767e09115a65c63b60" # v2
|
||||
- name: "Install shellcheck and shfmt"
|
||||
env:
|
||||
SHELLCHECK_VERSION: "v0.11.0"
|
||||
SHELLCHECK_SHA256: "8c3be12b05d5c177a04c29e3c78ce89ac86f1595681cab149b65b97c4e227198"
|
||||
SHFMT_VERSION: "v3.13.1"
|
||||
SHFMT_SHA256: "fb096c5d1ac6beabbdbaa2874d025badb03ee07929f0c9ff67563ce8c75398b1"
|
||||
run: |
|
||||
curl -sL "https://github.com/koalaman/shellcheck/releases/download/${SHELLCHECK_VERSION}/shellcheck-${SHELLCHECK_VERSION}.linux.x86_64.tar.xz" -o /tmp/shellcheck.tar.xz
|
||||
echo "${SHELLCHECK_SHA256} /tmp/shellcheck.tar.xz" | sha256sum -c -
|
||||
tar -xJf /tmp/shellcheck.tar.xz -C /tmp
|
||||
sudo install -m 755 "/tmp/shellcheck-${SHELLCHECK_VERSION}/shellcheck" /usr/local/bin/shellcheck
|
||||
curl -sL "https://github.com/mvdan/sh/releases/download/${SHFMT_VERSION}/shfmt_${SHFMT_VERSION}_linux_amd64" -o /tmp/shfmt
|
||||
echo "${SHFMT_SHA256} /tmp/shfmt" | sha256sum -c -
|
||||
sudo install -m 755 /tmp/shfmt /usr/local/bin/shfmt
|
||||
shellcheck --version
|
||||
shfmt --version
|
||||
- name: "Run make lint-shell"
|
||||
run: make lint-shell
|
||||
|
||||
test:
|
||||
name: "test"
|
||||
runs-on: "runs-on=${{ github.run_id }}/runner=4cpu-linux-x64/extras=s3-cache"
|
||||
|
||||
5
.shellcheckrc
Normal file
5
.shellcheckrc
Normal file
@@ -0,0 +1,5 @@
|
||||
# Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Prefer shebang-detected dialect (sh vs bash).
|
||||
external-sources=false
|
||||
35
GNUmakefile
35
GNUmakefile
@@ -25,6 +25,24 @@ SWIFTLINT_CONFIG ?= .swiftlint.yml
|
||||
|
||||
swift_sources = $(shell find $(SWIFT_ENROLL_UI) \( -name '*.swift' ! -name '*.generated.swift' ! -path '*/.build/*' \) | sort)
|
||||
|
||||
SHELLCHECKCMD ?= shellcheck
|
||||
SHFMTCMD ?= shfmt
|
||||
SHFMTFLAGS ?= -i 2 -ci -bn
|
||||
|
||||
# First-party shell scripts linted by lint-shell / fmt-shell (CI).
|
||||
# Add every new first-party *.sh here; do not include vendored/submodule scripts.
|
||||
SHELL_SCRIPTS := \
|
||||
cmd/probo-agent/installer/install.sh \
|
||||
cmd/probo-agent/installer/macos/build.sh \
|
||||
cmd/probo-agent/installer/macos/reinstall.sh \
|
||||
cmd/probo-agent/installer/macos/uninstall.sh \
|
||||
compose/postgres/01_probod.sh \
|
||||
contrib/lima/provision.sh \
|
||||
contrib/lima/sandbox.sh \
|
||||
contrib/merge-graphql-schema.sh \
|
||||
contrib/seed.sh \
|
||||
entrypoint.sh
|
||||
|
||||
DOCKER_BUILD_FLAGS?=
|
||||
DOCKER_BUILD= DOCKER_BUILDKIT=1 $(DOCKER) build $(DOCKER_BUILD_FLAGS)
|
||||
|
||||
@@ -137,6 +155,15 @@ swift-lint: ## Lint Swift with SwiftLint
|
||||
@command -v $(SWIFTLINTCMD) >/dev/null 2>&1 || { echo "error: '$(SWIFTLINTCMD)' not found; install SwiftLint (e.g. brew install swiftlint)"; exit 1; }
|
||||
$(SWIFTLINTCMD) lint --strict --config $(SWIFTLINT_CONFIG) --cache-path .cache/swiftlint
|
||||
|
||||
.PHONY: lint-shell
|
||||
lint-shell: ## Lint first-party shell scripts (shfmt + shellcheck)
|
||||
@if [ -z "$(SHELL_SCRIPTS)" ]; then \
|
||||
echo "error: no shell scripts found"; \
|
||||
exit 1; \
|
||||
fi
|
||||
$(SHFMTCMD) -d $(SHFMTFLAGS) $(SHELL_SCRIPTS)
|
||||
$(SHELLCHECKCMD) $(SHELL_SCRIPTS)
|
||||
|
||||
.PHONY: vet
|
||||
vet: generate embed
|
||||
$(GO_VET) ./...
|
||||
@@ -418,6 +445,14 @@ fmt-swift: ## Format Swift enroll-ui sources
|
||||
$(SWIFTLINTCMD) lint --fix --config $(SWIFTLINT_CONFIG) --cache-path .cache/swiftlint; \
|
||||
fi
|
||||
|
||||
.PHONY: fmt-shell
|
||||
fmt-shell: ## Format first-party shell scripts with shfmt
|
||||
@if [ -z "$(SHELL_SCRIPTS)" ]; then \
|
||||
echo "error: no shell scripts found"; \
|
||||
exit 1; \
|
||||
fi
|
||||
$(SHFMTCMD) -w $(SHFMTFLAGS) $(SHELL_SCRIPTS)
|
||||
|
||||
.PHONY: clean
|
||||
clean: ## Clean the project (node_modules and build artifacts)
|
||||
$(RM) -rf bin/*
|
||||
|
||||
@@ -61,12 +61,31 @@ usage() {
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--binary) BINARY="$2"; shift 2 ;;
|
||||
--version) VERSION="$2"; shift 2 ;;
|
||||
--output) OUTPUT="$2"; shift 2 ;;
|
||||
--identifier) IDENTIFIER="$2"; shift 2 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "unknown flag: $1" >&2; usage >&2; exit 2 ;;
|
||||
--binary)
|
||||
BINARY="$2"
|
||||
shift 2
|
||||
;;
|
||||
--version)
|
||||
VERSION="$2"
|
||||
shift 2
|
||||
;;
|
||||
--output)
|
||||
OUTPUT="$2"
|
||||
shift 2
|
||||
;;
|
||||
--identifier)
|
||||
IDENTIFIER="$2"
|
||||
shift 2
|
||||
;;
|
||||
-h | --help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "unknown flag: $1" >&2
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
|
||||
@@ -52,9 +52,9 @@ bootout_tray_for_user() {
|
||||
local username="$1"
|
||||
local user_uid
|
||||
|
||||
if [ -z "${username}" ] || \
|
||||
[ "${username}" = "root" ] || \
|
||||
[ "${username}" = "loginwindow" ]; then
|
||||
if [ -z "${username}" ] \
|
||||
|| [ "${username}" = "root" ] \
|
||||
|| [ "${username}" = "loginwindow" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
@@ -70,8 +70,7 @@ unregister_apps() {
|
||||
local path
|
||||
for path in \
|
||||
"/Applications/Probo Agent.app" \
|
||||
"/Applications/Probo Agent.localized/Probo Agent.app"
|
||||
do
|
||||
"/Applications/Probo Agent.localized/Probo Agent.app"; do
|
||||
if [ -d "${path}" ] && [ -x "${LSREGISTER}" ]; then
|
||||
"${LSREGISTER}" -u "${path}" 2>/dev/null || true
|
||||
log "Unregistered Launch Services entry for ${path}"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
set -eu
|
||||
|
||||
psql -v ON_ERROR_STOP=1 -U $POSTGRES_USER <<-EOF
|
||||
psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" <<-EOF
|
||||
CREATE USER probod;
|
||||
ALTER USER probod WITH SUPERUSER;
|
||||
ALTER USER probod PASSWORD 'probod';
|
||||
@@ -12,13 +12,13 @@ CREATE DATABASE probod_test;
|
||||
GRANT ALL PRIVILEGES ON DATABASE probod_test TO probod;
|
||||
EOF
|
||||
|
||||
psql -v ON_ERROR_STOP=1 -U $POSTGRES_USER -d probod <<-EOF
|
||||
psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d probod <<-EOF
|
||||
ALTER SCHEMA public OWNER TO probod;
|
||||
GRANT ALL ON SCHEMA public TO probod;
|
||||
ALTER DATABASE probod SET probo.trust_center_base_domain TO 'probopage.localhost';
|
||||
EOF
|
||||
|
||||
psql -v ON_ERROR_STOP=1 -U $POSTGRES_USER -d probod_test <<-EOF
|
||||
psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d probod_test <<-EOF
|
||||
ALTER SCHEMA public OWNER TO probod;
|
||||
GRANT ALL ON SCHEMA public TO probod;
|
||||
ALTER DATABASE probod_test SET probo.trust_center_base_domain TO 'probopage.localhost';
|
||||
|
||||
@@ -16,8 +16,10 @@ The project uses a `GNUmakefile` at the root. Builds run with `--jobs=$(nproc)`
|
||||
| `make test-e2e` | Run console end-to-end tests (requires `bin/probod`) |
|
||||
| `make lint` | Run Go + JS linters: `vet` + `go-fmt` + `go-fix` + `go-lint` + `lint-js` |
|
||||
| `make lint-swift` | Opt-in: lint Swift enroll-ui (`swift-fmt` + `swift-lint`; needs Swift + SwiftLint; CI runs this on Linux) |
|
||||
| `make lint-shell` | Opt-in: lint `SHELL_SCRIPTS` (`shfmt -d` + `shellcheck`; CI runs this) |
|
||||
| `make fmt` | Format Go code |
|
||||
| `make fmt-swift` | Opt-in: format Swift enroll-ui (`swift format` + SwiftLint `--fix`; needs Swift) |
|
||||
| `make fmt-shell` | Opt-in: format `SHELL_SCRIPTS` with `shfmt` |
|
||||
| `make clean` | Remove all build artifacts, `node_modules`, generated files, and coverage |
|
||||
| `make help` | List targets with `##` doc comments |
|
||||
|
||||
@@ -86,3 +88,10 @@ Individual codegen is driven by `go generate`:
|
||||
| `SWIFTLINTCMD` | `swiftlint` | SwiftLint binary |
|
||||
| `SWIFTCMD` | `swift` | Swift toolchain binary (`swift format`) |
|
||||
| `SWIFT_ENROLL_UI` | `cmd/probo-agent/installer/macos/enroll-ui` | Path to the Swift SPM package |
|
||||
| `SHELLCHECKCMD` | `shellcheck` | ShellCheck binary |
|
||||
| `SHFMTCMD` | `shfmt` | shfmt binary |
|
||||
| `SHFMTFLAGS` | `-i 2 -ci -bn` | Flags passed to `shfmt` |
|
||||
|
||||
## Shell scripts
|
||||
|
||||
`make lint-shell` / `make fmt-shell` only touch the static `SHELL_SCRIPTS` list in the root `GNUmakefile` (not a recursive `find`). When you add a new first-party `*.sh` file, append it to that list so CI formats and lint it. Do not add vendored or git-submodule scripts (for example under `pkg/validator/data/disposable-email-domains`).
|
||||
|
||||
@@ -15,8 +15,6 @@ GO_VERSION="1.26.5"
|
||||
NODE_MAJOR=24
|
||||
NPM_VERSION="11.8.0"
|
||||
|
||||
GOTESTSUM_VERSION="v1.13.0"
|
||||
GOLANGCI_LINT_VERSION="v2.11.3"
|
||||
GOW_VERSION="v0.0.0-20260225145757-ff0f6779ab4c"
|
||||
MKCERT_VERSION="v1.4.4"
|
||||
|
||||
@@ -109,6 +107,7 @@ fi
|
||||
# Load developer-specific overrides (not committed to repo).
|
||||
if [ -f /workspace/.sandbox.env ]; then
|
||||
set -a
|
||||
# shellcheck source=/dev/null
|
||||
. /workspace/.sandbox.env
|
||||
set +a
|
||||
fi
|
||||
|
||||
@@ -49,10 +49,22 @@ cmd_create() {
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--cpus) cpus="$2"; shift 2 ;;
|
||||
--memory) memory="$2"; shift 2 ;;
|
||||
--disk) disk="$2"; shift 2 ;;
|
||||
*) echo "Unknown option: $1"; usage ;;
|
||||
--cpus)
|
||||
cpus="$2"
|
||||
shift 2
|
||||
;;
|
||||
--memory)
|
||||
memory="$2"
|
||||
shift 2
|
||||
;;
|
||||
--disk)
|
||||
disk="$2"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
@@ -175,5 +187,8 @@ case "${command}" in
|
||||
;;
|
||||
status) cmd_status ;;
|
||||
list) cmd_list ;;
|
||||
*) echo "Unknown command: ${command}"; usage ;;
|
||||
*)
|
||||
echo "Unknown command: ${command}"
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
# GraphQL documents are intentional single-quoted literals (no expansion).
|
||||
# shellcheck disable=SC2016
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
BASE_URL="${PROBO_SEED_URL:-http://localhost:8080}"
|
||||
@@ -58,7 +61,8 @@ check_error() {
|
||||
}
|
||||
|
||||
prb_api() {
|
||||
local context="$1"; shift
|
||||
local context="$1"
|
||||
shift
|
||||
local resp
|
||||
resp=$($PRB api "$@")
|
||||
check_error "$resp" "$context"
|
||||
@@ -66,13 +70,17 @@ prb_api() {
|
||||
}
|
||||
|
||||
curl -sf -o /dev/null "$BASE_URL/healthz" \
|
||||
|| { echo "ERROR: API at $BASE_URL is not available" >&2; exit 1; }
|
||||
|| {
|
||||
echo "ERROR: API at $BASE_URL is not available" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "==> Bootstrapping user and organization..."
|
||||
vars=$(jo input="$(jo \
|
||||
vars=$(jo input="$(
|
||||
jo \
|
||||
email="$EMAIL" \
|
||||
password="$PASSWORD" \
|
||||
fullName="$FULL_NAME" \
|
||||
fullName="$FULL_NAME"
|
||||
)")
|
||||
resp=$(gql_connect '
|
||||
mutation($input: SignUpInput!) {
|
||||
@@ -96,9 +104,10 @@ check_error "$resp" "createOrganization"
|
||||
ORG_ID=$(echo "$resp" | jq -r '.data.createOrganization.organization.id')
|
||||
echo " Created organization $ORG_NAME ($ORG_ID)"
|
||||
|
||||
vars=$(jo input="$(jo \
|
||||
vars=$(jo input="$(
|
||||
jo \
|
||||
organizationId="$ORG_ID" \
|
||||
continue="$BASE_URL" \
|
||||
continue="$BASE_URL"
|
||||
)")
|
||||
resp=$(gql_connect '
|
||||
mutation($input: AssumeOrganizationSessionInput!) {
|
||||
@@ -116,9 +125,10 @@ echo " Assumed organization session"
|
||||
|
||||
EXPIRES_AT=$(date -u -v+1y +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null \
|
||||
|| date -u -d "+1 year" +"%Y-%m-%dT%H:%M:%SZ")
|
||||
vars=$(jo input="$(jo \
|
||||
vars=$(jo input="$(
|
||||
jo \
|
||||
name=seed \
|
||||
expiresAt="$EXPIRES_AT" \
|
||||
expiresAt="$EXPIRES_AT"
|
||||
)")
|
||||
resp=$(gql_connect '
|
||||
mutation($input: CreatePersonalAPIKeyInput!) {
|
||||
@@ -148,14 +158,15 @@ create_person() {
|
||||
local email="$3"
|
||||
|
||||
local vars
|
||||
vars=$(jo input="$(jo \
|
||||
vars=$(jo input="$(
|
||||
jo \
|
||||
organizationId="$ORG_ID" \
|
||||
emailAddress="$email" \
|
||||
fullName="$full_name" \
|
||||
role=EMPLOYEE \
|
||||
kind=EMPLOYEE \
|
||||
additionalEmailAddresses="$(jo -a </dev/null)" \
|
||||
position="$position" \
|
||||
position="$position"
|
||||
)")
|
||||
resp=$(gql_connect '
|
||||
mutation($input: CreateUserInput!) {
|
||||
@@ -219,10 +230,11 @@ create_framework() {
|
||||
}
|
||||
}
|
||||
}
|
||||
' -f input="$(jo \
|
||||
' -f input="$(
|
||||
jo \
|
||||
organizationId="$ORG_ID" \
|
||||
name="$name" \
|
||||
description="$desc" \
|
||||
description="$desc"
|
||||
)")
|
||||
local id
|
||||
id=$(echo "$resp" | jq -r '.data.createFramework.frameworkEdge.node.id // empty')
|
||||
@@ -586,10 +598,11 @@ create_third_party() {
|
||||
}
|
||||
}
|
||||
}
|
||||
' -f input="$(jo \
|
||||
' -f input="$(
|
||||
jo \
|
||||
organizationId="$ORG_ID" \
|
||||
name="$name" \
|
||||
description="$description" \
|
||||
description="$description"
|
||||
)")
|
||||
local id
|
||||
id=$(echo "$resp" | jq -r '.data.createThirdParty.thirdPartyEdge.node.id // empty')
|
||||
@@ -657,10 +670,11 @@ create_measure() {
|
||||
}
|
||||
}
|
||||
}
|
||||
' -f input="$(jo \
|
||||
' -f input="$(
|
||||
jo \
|
||||
organizationId="$ORG_ID" \
|
||||
name="$name" \
|
||||
category="$category" \
|
||||
category="$category"
|
||||
)")
|
||||
local id
|
||||
id=$(echo "$resp" | jq -r '.data.createMeasure.measureEdge.node.id // empty')
|
||||
@@ -796,7 +810,8 @@ agent_heartbeat() {
|
||||
|
||||
# agent_postures <api_key> <CHECK_KEY:STATUS>...
|
||||
agent_postures() {
|
||||
local api_key="$1"; shift
|
||||
local api_key="$1"
|
||||
shift
|
||||
|
||||
local now
|
||||
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
Reference in New Issue
Block a user