@blundergoat/gruff-ts 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/CONTRIBUTING.md +87 -0
- package/LICENSE +21 -0
- package/README.md +303 -0
- package/SECURITY.md +45 -0
- package/bin/gruff-ts +25 -0
- package/docs/CONFIGURATION.md +220 -0
- package/docs/RELEASING.md +103 -0
- package/docs/REPORTS_AND_CI.md +156 -0
- package/fixtures/sample.ts +21 -0
- package/package.json +56 -0
- package/scripts/bump-version.sh +145 -0
- package/scripts/check.sh +4 -0
- package/scripts/npm-publish.sh +258 -0
- package/scripts/preflight-checks.sh +357 -0
- package/scripts/start-dev.sh +8 -0
- package/scripts/test-performance.sh +695 -0
- package/src/analyser.ts +461 -0
- package/src/baseline.ts +90 -0
- package/src/blocks.ts +687 -0
- package/src/class-rules.ts +326 -0
- package/src/cli-program.ts +326 -0
- package/src/cli.ts +19 -0
- package/src/comment-rules.ts +605 -0
- package/src/comment-scanner.ts +357 -0
- package/src/config.ts +622 -0
- package/src/constants.ts +4 -0
- package/src/context-doc-rules.ts +241 -0
- package/src/dashboard.ts +114 -0
- package/src/dead-code-rules.ts +183 -0
- package/src/discovery.ts +508 -0
- package/src/doc-rules.ts +368 -0
- package/src/findings-helpers.ts +108 -0
- package/src/findings.ts +45 -0
- package/src/fixture-purpose-rules.ts +334 -0
- package/src/fixtures/rule-catalogue-security-doctrine.ts +132 -0
- package/src/github-actions-rules.ts +413 -0
- package/src/line-rules.ts +538 -0
- package/src/naming-pushers.ts +191 -0
- package/src/project-config-rules.ts +555 -0
- package/src/project-rules.ts +545 -0
- package/src/report-renderers.ts +691 -0
- package/src/rule-list.ts +179 -0
- package/src/rules.ts +135 -0
- package/src/safety-rules.ts +355 -0
- package/src/scoring.ts +74 -0
- package/src/security-flow-rules.ts +112 -0
- package/src/sensitive-data-rules.ts +288 -0
- package/src/source-text.ts +722 -0
- package/src/test-block-rules.ts +347 -0
- package/src/test-fixtures.ts +621 -0
- package/src/text-scans.ts +193 -0
- package/src/types.ts +113 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# npm-publish.sh
|
|
3
|
+
#
|
|
4
|
+
# Purpose:
|
|
5
|
+
# Release gruff-ts to npm with local preflight checks and human confirmation.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# bash scripts/npm-publish.sh
|
|
9
|
+
#
|
|
10
|
+
# Behavior:
|
|
11
|
+
# 1) reads package.json name/version
|
|
12
|
+
# 2) verifies npm authentication
|
|
13
|
+
# 3) checks version lockstep and runs the release preflight gate
|
|
14
|
+
# 4) prints a dry-run publish summary
|
|
15
|
+
# 5) asks for manual confirmation before `npm publish`
|
|
16
|
+
#
|
|
17
|
+
# Exit:
|
|
18
|
+
# 0 if published or explicitly aborted, non-zero on failed auth/preflight/publish.
|
|
19
|
+
#
|
|
20
|
+
# Requirements:
|
|
21
|
+
# - node, npm
|
|
22
|
+
# - npm publish authentication:
|
|
23
|
+
# npm package publishing requires either account 2FA for the publish
|
|
24
|
+
# prompt or a granular access token with Bypass 2FA enabled.
|
|
25
|
+
#
|
|
26
|
+
# Preferred token setup for this script:
|
|
27
|
+
# 1. In npmjs.com, open Account -> Access Tokens.
|
|
28
|
+
# 2. Generate a Granular Access Token.
|
|
29
|
+
# 3. Grant read/write access to gruff-ts, or to all packages for the
|
|
30
|
+
# publishing account.
|
|
31
|
+
# 4. Enable Bypass 2FA for write actions.
|
|
32
|
+
# 5. Either store it in your npm user config:
|
|
33
|
+
# npm config set //registry.npmjs.org/:_authToken=npm_...
|
|
34
|
+
# bash scripts/npm-publish.sh
|
|
35
|
+
#
|
|
36
|
+
# Or keep it out of ~/.npmrc and pass it for this shell only:
|
|
37
|
+
# export NPM_TOKEN="npm_..."
|
|
38
|
+
# bash scripts/npm-publish.sh
|
|
39
|
+
#
|
|
40
|
+
# NODE_AUTH_TOKEN is also accepted for the temporary-token path. The
|
|
41
|
+
# script writes env tokens to a temporary npm config file and removes it
|
|
42
|
+
# on exit. Do not commit an .npmrc containing a real token.
|
|
43
|
+
set -euo pipefail
|
|
44
|
+
|
|
45
|
+
REGISTRY_URL="https://registry.npmjs.org/"
|
|
46
|
+
AUTH_SOURCE=""
|
|
47
|
+
TEMP_NPMRC=""
|
|
48
|
+
PACKAGE_NAME=""
|
|
49
|
+
|
|
50
|
+
usage() {
|
|
51
|
+
cat <<'USAGE'
|
|
52
|
+
Usage:
|
|
53
|
+
bash scripts/npm-publish.sh
|
|
54
|
+
bash scripts/npm-publish.sh --help
|
|
55
|
+
|
|
56
|
+
Publishes the current package version to npm after:
|
|
57
|
+
- npm auth/token validation
|
|
58
|
+
- scripts/bump-version.sh --check
|
|
59
|
+
- scripts/preflight-checks.sh
|
|
60
|
+
- npm publish --dry-run
|
|
61
|
+
|
|
62
|
+
The final publish still requires an interactive y/N confirmation.
|
|
63
|
+
USAGE
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
die() {
|
|
67
|
+
printf 'npm-publish: %s\n' "$*" >&2
|
|
68
|
+
exit 1
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
repo_root() {
|
|
72
|
+
local script_dir
|
|
73
|
+
script_dir="$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
74
|
+
CDPATH='' cd -- "$script_dir/.." && pwd
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
read_package_name() {
|
|
78
|
+
node -p "require('./package.json').name"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
read_package_version() {
|
|
82
|
+
node -p "require('./package.json').version"
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
cleanup() {
|
|
86
|
+
if [[ -n "$TEMP_NPMRC" && -f "$TEMP_NPMRC" ]]; then
|
|
87
|
+
rm -f -- "$TEMP_NPMRC"
|
|
88
|
+
fi
|
|
89
|
+
}
|
|
90
|
+
trap cleanup EXIT
|
|
91
|
+
|
|
92
|
+
print_token_instructions() {
|
|
93
|
+
local reason="$1"
|
|
94
|
+
|
|
95
|
+
printf 'Error: %s\n' "$reason" >&2
|
|
96
|
+
cat >&2 <<EOF
|
|
97
|
+
|
|
98
|
+
Publish token setup:
|
|
99
|
+
1. Go to npmjs.com -> Account -> Access Tokens.
|
|
100
|
+
2. Generate a Granular Access Token.
|
|
101
|
+
3. Grant read/write access to ${PACKAGE_NAME}, or to all packages for the
|
|
102
|
+
publishing account.
|
|
103
|
+
4. Enable Bypass 2FA for write actions.
|
|
104
|
+
5. Store the token in your npm user config:
|
|
105
|
+
|
|
106
|
+
npm config set //registry.npmjs.org/:_authToken=npm_...
|
|
107
|
+
bash scripts/npm-publish.sh
|
|
108
|
+
|
|
109
|
+
This is simple, but it persists the token in ~/.npmrc.
|
|
110
|
+
|
|
111
|
+
To avoid storing the token, pass it for this shell only:
|
|
112
|
+
|
|
113
|
+
export NPM_TOKEN="npm_..."
|
|
114
|
+
bash scripts/npm-publish.sh
|
|
115
|
+
|
|
116
|
+
NODE_AUTH_TOKEN works too:
|
|
117
|
+
|
|
118
|
+
export NODE_AUTH_TOKEN="npm_..."
|
|
119
|
+
bash scripts/npm-publish.sh
|
|
120
|
+
|
|
121
|
+
Do not commit an .npmrc containing a real token.
|
|
122
|
+
EOF
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
configure_token_from_env() {
|
|
126
|
+
local token_source=""
|
|
127
|
+
local token_value=""
|
|
128
|
+
|
|
129
|
+
if [[ -n "${NPM_TOKEN:-}" ]]; then
|
|
130
|
+
token_source="NPM_TOKEN"
|
|
131
|
+
token_value="$NPM_TOKEN"
|
|
132
|
+
elif [[ -n "${NODE_AUTH_TOKEN:-}" ]]; then
|
|
133
|
+
token_source="NODE_AUTH_TOKEN"
|
|
134
|
+
token_value="$NODE_AUTH_TOKEN"
|
|
135
|
+
else
|
|
136
|
+
return 1
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
TEMP_NPMRC=$(mktemp)
|
|
140
|
+
chmod 0600 "$TEMP_NPMRC"
|
|
141
|
+
{
|
|
142
|
+
printf 'registry=%s\n' "$REGISTRY_URL"
|
|
143
|
+
printf '//registry.npmjs.org/:_authToken=%s\n' "$token_value"
|
|
144
|
+
} >"$TEMP_NPMRC"
|
|
145
|
+
|
|
146
|
+
export NPM_CONFIG_USERCONFIG="$TEMP_NPMRC"
|
|
147
|
+
AUTH_SOURCE="$token_source"
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
has_configured_registry_token() {
|
|
151
|
+
npm config list 2>/dev/null | grep -Eq '^//registry\.npmjs\.org/:_authToken = \(protected\)$'
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
trim_output() {
|
|
155
|
+
tr -d '\r' | awk '{$1=$1; print}'
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
verify_publish_auth() {
|
|
159
|
+
local npm_user
|
|
160
|
+
local tfa_mode
|
|
161
|
+
|
|
162
|
+
echo "--- Auth check ---"
|
|
163
|
+
if configure_token_from_env; then
|
|
164
|
+
echo "Using ${AUTH_SOURCE} via temporary npm config."
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
if ! npm_user=$(npm whoami --registry="$REGISTRY_URL" 2>/dev/null); then
|
|
168
|
+
print_token_instructions "npm is not authenticated for ${REGISTRY_URL}."
|
|
169
|
+
exit 1
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
echo "Logged in as: ${npm_user}"
|
|
173
|
+
|
|
174
|
+
if [[ -n "$AUTH_SOURCE" ]]; then
|
|
175
|
+
echo "Publish token source: ${AUTH_SOURCE}"
|
|
176
|
+
echo "Token must be granular, read/write for ${PACKAGE_NAME}, and Bypass 2FA enabled."
|
|
177
|
+
echo ""
|
|
178
|
+
return 0
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
if has_configured_registry_token; then
|
|
182
|
+
AUTH_SOURCE="npm user config"
|
|
183
|
+
echo "Publish token source: ${AUTH_SOURCE}"
|
|
184
|
+
echo "Token must be granular, read/write for ${PACKAGE_NAME}, and Bypass 2FA enabled."
|
|
185
|
+
echo ""
|
|
186
|
+
return 0
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
if ! tfa_mode=$(npm profile get "two-factor auth" --registry="$REGISTRY_URL" 2>/dev/null | trim_output); then
|
|
190
|
+
print_token_instructions "unable to verify npm 2FA state and no publish token was supplied."
|
|
191
|
+
exit 1
|
|
192
|
+
fi
|
|
193
|
+
|
|
194
|
+
if [[ -z "$tfa_mode" ]]; then
|
|
195
|
+
print_token_instructions "npm did not report an account 2FA mode and no publish token was supplied."
|
|
196
|
+
exit 1
|
|
197
|
+
fi
|
|
198
|
+
|
|
199
|
+
echo "Account 2FA mode: ${tfa_mode}"
|
|
200
|
+
if [[ "$tfa_mode" == "disabled" ]]; then
|
|
201
|
+
print_token_instructions "npm account 2FA is disabled and no publish token was supplied."
|
|
202
|
+
exit 1
|
|
203
|
+
fi
|
|
204
|
+
|
|
205
|
+
echo "No explicit publish token supplied; npm must complete the publish with an interactive OTP."
|
|
206
|
+
echo ""
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
main() {
|
|
210
|
+
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|
211
|
+
usage
|
|
212
|
+
exit 0
|
|
213
|
+
fi
|
|
214
|
+
|
|
215
|
+
if [[ "$#" -ne 0 ]]; then
|
|
216
|
+
usage >&2
|
|
217
|
+
die "unknown argument: $1"
|
|
218
|
+
fi
|
|
219
|
+
|
|
220
|
+
cd "$(repo_root)"
|
|
221
|
+
[[ -f package.json ]] || die "package.json not found"
|
|
222
|
+
|
|
223
|
+
PACKAGE_NAME="$(read_package_name)" || die "failed to read package.json name"
|
|
224
|
+
local version
|
|
225
|
+
version="$(read_package_version)" || die "failed to read package.json version"
|
|
226
|
+
[[ -n "$PACKAGE_NAME" ]] || die "package.json has no name field"
|
|
227
|
+
[[ -n "$version" ]] || die "package.json has no version field"
|
|
228
|
+
|
|
229
|
+
local publish_args=("--registry=$REGISTRY_URL")
|
|
230
|
+
if [[ "$PACKAGE_NAME" == @*/* ]]; then
|
|
231
|
+
publish_args+=(--access public)
|
|
232
|
+
fi
|
|
233
|
+
|
|
234
|
+
echo "Publishing ${PACKAGE_NAME}@${version}"
|
|
235
|
+
verify_publish_auth
|
|
236
|
+
|
|
237
|
+
echo "--- Preflight ---"
|
|
238
|
+
bash scripts/bump-version.sh --check
|
|
239
|
+
bash scripts/preflight-checks.sh
|
|
240
|
+
echo ""
|
|
241
|
+
|
|
242
|
+
echo "--- Dry run ---"
|
|
243
|
+
npm publish --dry-run "${publish_args[@]}"
|
|
244
|
+
echo ""
|
|
245
|
+
|
|
246
|
+
local confirm
|
|
247
|
+
read -rp "Publish ${PACKAGE_NAME}@${version} to npm? (y/N) " confirm
|
|
248
|
+
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
|
|
249
|
+
echo "Aborted."
|
|
250
|
+
exit 0
|
|
251
|
+
fi
|
|
252
|
+
|
|
253
|
+
npm publish "${publish_args[@]}"
|
|
254
|
+
echo ""
|
|
255
|
+
echo "Published: https://www.npmjs.com/package/${PACKAGE_NAME}/v/${version}"
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
main "$@"
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
REPO_ROOT="$(CDPATH='' cd -- "$SCRIPT_DIR/.." && pwd)"
|
|
6
|
+
|
|
7
|
+
if [[ -t 1 && -z "${NO_COLOR:-}" ]]; then
|
|
8
|
+
BOLD=$'\033[1m'
|
|
9
|
+
DIM=$'\033[2m'
|
|
10
|
+
GREEN=$'\033[32m'
|
|
11
|
+
RED=$'\033[31m'
|
|
12
|
+
YELLOW=$'\033[33m'
|
|
13
|
+
BLUE=$'\033[34m'
|
|
14
|
+
RESET=$'\033[0m'
|
|
15
|
+
else
|
|
16
|
+
BOLD=''
|
|
17
|
+
DIM=''
|
|
18
|
+
GREEN=''
|
|
19
|
+
RED=''
|
|
20
|
+
YELLOW=''
|
|
21
|
+
BLUE=''
|
|
22
|
+
RESET=''
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
PASS="${GREEN}OK${RESET}"
|
|
26
|
+
FAIL="${RED}FAIL${RESET}"
|
|
27
|
+
SKIP="${YELLOW}SKIP${RESET}"
|
|
28
|
+
ARROW="${BLUE}->${RESET}"
|
|
29
|
+
|
|
30
|
+
TOTAL=0
|
|
31
|
+
PASSED=0
|
|
32
|
+
FAILED=0
|
|
33
|
+
FAILURES=()
|
|
34
|
+
TMP_FILES=()
|
|
35
|
+
START_TIME=$(date +%s%N)
|
|
36
|
+
|
|
37
|
+
usage() {
|
|
38
|
+
cat <<'USAGE'
|
|
39
|
+
Usage:
|
|
40
|
+
scripts/preflight-checks.sh
|
|
41
|
+
|
|
42
|
+
Runs the local preflight gate:
|
|
43
|
+
- npm run check (TypeScript compile plus unit tests)
|
|
44
|
+
- gruff-ts full-project scan
|
|
45
|
+
- shellcheck for scripts/*.sh when shellcheck is installed
|
|
46
|
+
|
|
47
|
+
Environment:
|
|
48
|
+
GRUFF_TS_FAIL_ON gruff-ts severity that fails static analysis (default: advisory)
|
|
49
|
+
USAGE
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
cleanup() {
|
|
53
|
+
local temp_file
|
|
54
|
+
|
|
55
|
+
for temp_file in "${TMP_FILES[@]}"; do
|
|
56
|
+
[[ -f "$temp_file" ]] && rm -f -- "$temp_file"
|
|
57
|
+
done
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
trap cleanup EXIT
|
|
61
|
+
|
|
62
|
+
rule() {
|
|
63
|
+
printf ' %s\n' "${DIM}--------------------------------------------${RESET}"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
elapsed_since() {
|
|
67
|
+
local started_at="$1"
|
|
68
|
+
local finished_at
|
|
69
|
+
local elapsed_ms
|
|
70
|
+
local seconds
|
|
71
|
+
local minutes
|
|
72
|
+
local remainder
|
|
73
|
+
local frac
|
|
74
|
+
|
|
75
|
+
finished_at=$(date +%s%N)
|
|
76
|
+
elapsed_ms=$(((finished_at - started_at) / 1000000))
|
|
77
|
+
|
|
78
|
+
if ((elapsed_ms < 1000)); then
|
|
79
|
+
printf '%dms' "$elapsed_ms"
|
|
80
|
+
return
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
seconds=$((elapsed_ms / 1000))
|
|
84
|
+
frac=$(((elapsed_ms % 1000) / 100))
|
|
85
|
+
|
|
86
|
+
if ((seconds < 60)); then
|
|
87
|
+
printf '%d.%ds' "$seconds" "$frac"
|
|
88
|
+
return
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
minutes=$((seconds / 60))
|
|
92
|
+
remainder=$((seconds % 60))
|
|
93
|
+
printf '%dm %02d.%ds' "$minutes" "$remainder" "$frac"
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
header() {
|
|
97
|
+
printf '\n'
|
|
98
|
+
printf ' %sPreflight Check%s\n' "$BOLD" "$RESET"
|
|
99
|
+
printf ' %s%s%s\n' "$DIM" "$(date '+%Y-%m-%d %H:%M:%S')" "$RESET"
|
|
100
|
+
rule
|
|
101
|
+
printf '\n'
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
step() {
|
|
105
|
+
local label="$1"
|
|
106
|
+
|
|
107
|
+
TOTAL=$((TOTAL + 1))
|
|
108
|
+
printf ' %s %-36s' "$ARROW" "$label"
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
pass() {
|
|
112
|
+
local detail="${1:-}"
|
|
113
|
+
|
|
114
|
+
PASSED=$((PASSED + 1))
|
|
115
|
+
if [[ -n "$detail" ]]; then
|
|
116
|
+
printf '%s %s%s%s\n' "$PASS" "$DIM" "$detail" "$RESET"
|
|
117
|
+
else
|
|
118
|
+
printf '%s\n' "$PASS"
|
|
119
|
+
fi
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
fail() {
|
|
123
|
+
local label="$1"
|
|
124
|
+
|
|
125
|
+
FAILED=$((FAILED + 1))
|
|
126
|
+
FAILURES+=("$label")
|
|
127
|
+
printf '%s\n' "$FAIL"
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
skip() {
|
|
131
|
+
local reason="${1:-skipped}"
|
|
132
|
+
|
|
133
|
+
printf '%s %s%s%s\n' "$SKIP" "$DIM" "$reason" "$RESET"
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
indent_output() {
|
|
137
|
+
while IFS= read -r line; do
|
|
138
|
+
printf ' %s%s%s\n' "$DIM" "$line" "$RESET"
|
|
139
|
+
done
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
run_step() {
|
|
143
|
+
local label="$1"
|
|
144
|
+
shift
|
|
145
|
+
|
|
146
|
+
local started_at
|
|
147
|
+
local output
|
|
148
|
+
local status
|
|
149
|
+
local elapsed
|
|
150
|
+
|
|
151
|
+
step "$label"
|
|
152
|
+
started_at=$(date +%s%N)
|
|
153
|
+
output=$("$@" 2>&1)
|
|
154
|
+
status=$?
|
|
155
|
+
elapsed=$(elapsed_since "$started_at")
|
|
156
|
+
|
|
157
|
+
if ((status == 0)); then
|
|
158
|
+
pass "${output:+$output }$elapsed"
|
|
159
|
+
else
|
|
160
|
+
fail "$label"
|
|
161
|
+
if [[ -n "$output" ]]; then
|
|
162
|
+
printf '%s\n' "$output" | tail -20 | indent_output
|
|
163
|
+
fi
|
|
164
|
+
printf ' %sexit %d after %s%s\n' "$DIM" "$status" "$elapsed" "$RESET"
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
return "$status"
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
make_temp_file() {
|
|
171
|
+
local suffix="$1"
|
|
172
|
+
local temp_file
|
|
173
|
+
|
|
174
|
+
temp_file=$(mktemp "${TMPDIR:-/tmp}/gruff-ts-preflight.XXXXXX.$suffix") || return 1
|
|
175
|
+
TMP_FILES+=("$temp_file")
|
|
176
|
+
printf '%s\n' "$temp_file"
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
npm_check() {
|
|
180
|
+
local output
|
|
181
|
+
local status
|
|
182
|
+
local tests
|
|
183
|
+
local passed
|
|
184
|
+
local failed
|
|
185
|
+
|
|
186
|
+
output=$(npm run check 2>&1)
|
|
187
|
+
status=$?
|
|
188
|
+
|
|
189
|
+
if ((status != 0)); then
|
|
190
|
+
printf '%s\n' "$output"
|
|
191
|
+
return "$status"
|
|
192
|
+
fi
|
|
193
|
+
|
|
194
|
+
tests=$(printf '%s\n' "$output" | awk '/^# tests / { print $3; exit }')
|
|
195
|
+
passed=$(printf '%s\n' "$output" | awk '/^# pass / { print $3; exit }')
|
|
196
|
+
failed=$(printf '%s\n' "$output" | awk '/^# fail / { print $3; exit }')
|
|
197
|
+
|
|
198
|
+
if [[ -n "$tests" && -n "$passed" ]]; then
|
|
199
|
+
printf '%s/%s tests passed' "$passed" "$tests"
|
|
200
|
+
if [[ -n "$failed" && "$failed" != "0" ]]; then
|
|
201
|
+
printf ', %s failed' "$failed"
|
|
202
|
+
fi
|
|
203
|
+
else
|
|
204
|
+
printf 'completed'
|
|
205
|
+
fi
|
|
206
|
+
|
|
207
|
+
return 0
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
gruff_report_summary() {
|
|
211
|
+
local report_path="$1"
|
|
212
|
+
|
|
213
|
+
# shellcheck disable=SC2016
|
|
214
|
+
node --input-type=module -e '
|
|
215
|
+
import { readFileSync } from "node:fs";
|
|
216
|
+
|
|
217
|
+
const report = JSON.parse(readFileSync(process.argv[1], "utf8"));
|
|
218
|
+
const summary = report.summary ?? {};
|
|
219
|
+
const score = report.score ?? {};
|
|
220
|
+
const paths = report.paths ?? {};
|
|
221
|
+
const total = Number(summary.total ?? 0);
|
|
222
|
+
const advisory = Number(summary.advisory ?? 0);
|
|
223
|
+
const warning = Number(summary.warning ?? 0);
|
|
224
|
+
const error = Number(summary.error ?? 0);
|
|
225
|
+
const grade = String(score.grade ?? "n/a");
|
|
226
|
+
const composite = Number(score.composite ?? 0).toFixed(1);
|
|
227
|
+
const analysedFiles = Number(paths.analysedFiles ?? 0);
|
|
228
|
+
|
|
229
|
+
console.log(`${total} findings (advisory=${advisory}, warning=${warning}, error=${error}), ${grade} ${composite}/100, ${analysedFiles} files`);
|
|
230
|
+
' "$report_path"
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
gruff_ts_check() {
|
|
234
|
+
local gruff_fail_on="${GRUFF_TS_FAIL_ON:-advisory}"
|
|
235
|
+
local report_path
|
|
236
|
+
local error_path
|
|
237
|
+
local status
|
|
238
|
+
local summary_status=0
|
|
239
|
+
local printed=0
|
|
240
|
+
|
|
241
|
+
report_path=$(make_temp_file json) || return 1
|
|
242
|
+
error_path=$(make_temp_file err) || return 1
|
|
243
|
+
|
|
244
|
+
./bin/gruff-ts analyse . --format=json --fail-on="$gruff_fail_on" --no-baseline >"$report_path" 2>"$error_path"
|
|
245
|
+
status=$?
|
|
246
|
+
|
|
247
|
+
if [[ -s "$report_path" ]]; then
|
|
248
|
+
gruff_report_summary "$report_path"
|
|
249
|
+
summary_status=$?
|
|
250
|
+
printed=1
|
|
251
|
+
fi
|
|
252
|
+
|
|
253
|
+
if [[ -s "$error_path" ]]; then
|
|
254
|
+
if ((printed)); then
|
|
255
|
+
printf '\n'
|
|
256
|
+
fi
|
|
257
|
+
cat "$error_path"
|
|
258
|
+
fi
|
|
259
|
+
|
|
260
|
+
if ((status != 0)); then
|
|
261
|
+
return "$status"
|
|
262
|
+
fi
|
|
263
|
+
return "$summary_status"
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
shellcheck_check() {
|
|
267
|
+
local scripts=()
|
|
268
|
+
local script_path
|
|
269
|
+
local output
|
|
270
|
+
local status
|
|
271
|
+
|
|
272
|
+
while IFS= read -r -d '' script_path; do
|
|
273
|
+
scripts+=("$script_path")
|
|
274
|
+
done < <(find scripts -maxdepth 1 -type f -name '*.sh' -print0 | sort -z)
|
|
275
|
+
|
|
276
|
+
if [[ "${#scripts[@]}" -eq 0 ]]; then
|
|
277
|
+
printf 'no scripts/*.sh files found'
|
|
278
|
+
return 0
|
|
279
|
+
fi
|
|
280
|
+
|
|
281
|
+
output=$(shellcheck "${scripts[@]}" 2>&1)
|
|
282
|
+
status=$?
|
|
283
|
+
|
|
284
|
+
if ((status == 0)); then
|
|
285
|
+
printf '%d scripts checked' "${#scripts[@]}"
|
|
286
|
+
else
|
|
287
|
+
printf '%s\n' "$output"
|
|
288
|
+
fi
|
|
289
|
+
|
|
290
|
+
return "$status"
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
summary() {
|
|
294
|
+
local elapsed
|
|
295
|
+
|
|
296
|
+
elapsed=$(elapsed_since "$START_TIME")
|
|
297
|
+
printf '\n'
|
|
298
|
+
rule
|
|
299
|
+
printf '\n'
|
|
300
|
+
|
|
301
|
+
if ((FAILED == 0)); then
|
|
302
|
+
printf ' %sAll %d/%d checks passed%s %s(%s)%s\n' "$GREEN$BOLD" "$PASSED" "$TOTAL" "$RESET" "$DIM" "$elapsed" "$RESET"
|
|
303
|
+
printf '\n'
|
|
304
|
+
return 0
|
|
305
|
+
fi
|
|
306
|
+
|
|
307
|
+
printf ' %s%d/%d checks failed%s %s(%s)%s\n' "$RED$BOLD" "$FAILED" "$TOTAL" "$RESET" "$DIM" "$elapsed" "$RESET"
|
|
308
|
+
printf '\n'
|
|
309
|
+
|
|
310
|
+
local failure
|
|
311
|
+
for failure in "${FAILURES[@]}"; do
|
|
312
|
+
printf ' %s %s\n' "$FAIL" "$failure"
|
|
313
|
+
done
|
|
314
|
+
printf '\n'
|
|
315
|
+
|
|
316
|
+
return 1
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
main() {
|
|
320
|
+
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|
321
|
+
usage
|
|
322
|
+
return 0
|
|
323
|
+
fi
|
|
324
|
+
|
|
325
|
+
if [[ "$#" -ne 0 ]]; then
|
|
326
|
+
printf '%sUnknown argument:%s %s\n' "$RED" "$RESET" "$1" >&2
|
|
327
|
+
usage >&2
|
|
328
|
+
return 64
|
|
329
|
+
fi
|
|
330
|
+
|
|
331
|
+
cd "$REPO_ROOT" || return 1
|
|
332
|
+
|
|
333
|
+
header
|
|
334
|
+
|
|
335
|
+
if [[ ! -x ./bin/gruff-ts ]]; then
|
|
336
|
+
step "gruff-ts binary"
|
|
337
|
+
fail "gruff-ts binary"
|
|
338
|
+
printf ' %s./bin/gruff-ts is missing or not executable%s\n' "$DIM" "$RESET"
|
|
339
|
+
summary
|
|
340
|
+
return 127
|
|
341
|
+
fi
|
|
342
|
+
|
|
343
|
+
run_step "TypeScript + tests" npm_check
|
|
344
|
+
|
|
345
|
+
run_step "Gruff full-project scan" gruff_ts_check
|
|
346
|
+
|
|
347
|
+
if command -v shellcheck >/dev/null 2>&1; then
|
|
348
|
+
run_step "Shell scripts (shellcheck)" shellcheck_check
|
|
349
|
+
else
|
|
350
|
+
step "Shell scripts (shellcheck)"
|
|
351
|
+
skip "shellcheck not found"
|
|
352
|
+
fi
|
|
353
|
+
|
|
354
|
+
summary
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
main "$@"
|