Three core pages and four plugin pages each imported qrcode and jsbarcode directly, and each carried its own answer to the same questions: what margin, what width, which error correction, how big a module must be before a scanner can read it. The answers had already drifted - margin 0 in one place and 2 in another, width 150 against 160 - and on a label that is the difference between a sticker that scans and one that does not. frontend/src/utils/codes.js owns it now: the label-stock presets, the quiet-zone and margin defaults, CODE128 with no printed value, and the printer-resolution arithmetic that only the Tech Tools generator had. A view passes what is specific to its own label and nothing else - MachineBadge still asks for CODE39, because the badge readers predate the shop-floor scanners and decode nothing else, and that is exactly the kind of thing a call site should say out loud. views/print/qrLogo.js is folded in rather than left as a second half-shared helper that only some of the pages reached into. The check script now fails a build that imports either library outside that module. Without it this re-forks within a month: the next label page starts by copying the nearest existing one, which is how it happened the first time. Tests cover the part no amount of looking at a screen verifies - a QR that looks fine at 96 dpi on a monitor can be unreadable at 203 dpi on half-inch stock.
220 lines
9.1 KiB
Bash
Executable File
220 lines
9.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Pre-commit naming + style check for shopdb-flask.
|
|
# Enforces CONTRIBUTING.md rules:
|
|
# 1. No non-ASCII chars in source (em-dashes, smart quotes, arrows, emojis)
|
|
# 2. No banned shorthand identifiers (cfg, ctx, mgr, req, res, env, util, helper)
|
|
# as standalone names (suffix usage like printers_bp, request_obj is allowed)
|
|
# 3. No snake_case DB column names in __tablename__ or db.Column attrs
|
|
# 4. No snake_case API params in frontend that should match DB column names
|
|
#
|
|
# Exits non-zero if any violation found.
|
|
# Skips: venv/, node_modules/, __pycache__/, frontend/dist/, migrations/versions/,
|
|
# deploy/windows/installer/bundle/ (installer build output)
|
|
|
|
set -e
|
|
|
|
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo .)"
|
|
cd "$REPO_ROOT"
|
|
|
|
VIOLATIONS=0
|
|
|
|
EXCLUDES=(
|
|
--exclude-dir=venv
|
|
--exclude-dir=node_modules
|
|
--exclude-dir=__pycache__
|
|
--exclude-dir=dist
|
|
# The installer's build output: a staged copy of the whole application plus
|
|
# a second SPA build under dist-subpath, which --exclude-dir=dist does not
|
|
# match. Linting it means linting vendored minified JS and failing on
|
|
# characters nobody in this repository wrote.
|
|
--exclude-dir=bundle
|
|
--exclude-dir=dist-subpath
|
|
--exclude-dir=.git
|
|
--exclude-dir=versions
|
|
--exclude-dir=staticdocs
|
|
)
|
|
|
|
INCLUDES_CODE=(
|
|
--include='*.py'
|
|
--include='*.vue'
|
|
--include='*.js'
|
|
--include='*.ts'
|
|
)
|
|
|
|
INCLUDES_ALL=(
|
|
--include='*.py'
|
|
--include='*.vue'
|
|
--include='*.js'
|
|
--include='*.ts'
|
|
--include='*.json'
|
|
--include='*.md'
|
|
--include='*.yaml'
|
|
--include='*.yml'
|
|
)
|
|
|
|
# INCLUDES_ALL, not INCLUDES_CODE: the check only ever covered .py/.vue/.js/.ts,
|
|
# so documentation was free to accumulate em-dashes, arrows and smart quotes -
|
|
# and did, including in files this repo's own convention forbids them in.
|
|
# Markdown, JSON and YAML are now covered too.
|
|
echo "==> Checking for non-ASCII characters..."
|
|
NON_ASCII=$(grep -rPn '[^\x00-\x7F]' "${EXCLUDES[@]}" "${INCLUDES_ALL[@]}" . 2>/dev/null || true)
|
|
if [ -n "$NON_ASCII" ]; then
|
|
echo "FAIL: non-ASCII characters found (em-dashes, smart quotes, arrows, emojis):"
|
|
echo "$NON_ASCII"
|
|
echo
|
|
VIOLATIONS=$((VIOLATIONS + 1))
|
|
fi
|
|
|
|
echo "==> Checking for banned shorthand (standalone)..."
|
|
# Match the word as a standalone identifier: not preceded or followed by underscore/word char
|
|
# Word boundary in grep is \b but we want to exclude suffix usage like printers_bp
|
|
# So: match (^|[^a-zA-Z0-9_])(banned)([^a-zA-Z0-9_]|$)
|
|
for word in cfg ctx mgr req res; do
|
|
HITS=$(grep -rPn "(^|[^a-zA-Z0-9_])${word}([^a-zA-Z0-9_]|\$)" "${EXCLUDES[@]}" --include='*.py' --include='*.vue' --include='*.js' --include='*.ts' . 2>/dev/null \
|
|
| grep -vP "(^|[^a-zA-Z0-9_])(request_obj|response_obj)" \
|
|
|| true)
|
|
if [ -n "$HITS" ]; then
|
|
echo "FAIL: banned shorthand '$word' (standalone) found:"
|
|
echo "$HITS"
|
|
echo
|
|
VIOLATIONS=$((VIOLATIONS + 1))
|
|
fi
|
|
done
|
|
|
|
echo "==> Checking for snake_case DB tablenames..."
|
|
SNAKE_TABLES=$(grep -rPn "__tablename__\s*=\s*['\"][^'\"]*_" "${EXCLUDES[@]}" --include='*.py' . 2>/dev/null || true)
|
|
if [ -n "$SNAKE_TABLES" ]; then
|
|
echo "FAIL: snake_case __tablename__ found (must be lowercase concatenated):"
|
|
echo "$SNAKE_TABLES"
|
|
echo
|
|
VIOLATIONS=$((VIOLATIONS + 1))
|
|
fi
|
|
|
|
echo "==> Checking for snake_case DB column attrs..."
|
|
SNAKE_COLS=$(grep -rPn "^\s+[a-z]+_[a-z_]+\s*=\s*db\.Column" "${EXCLUDES[@]}" --include='*.py' . 2>/dev/null || true)
|
|
if [ -n "$SNAKE_COLS" ]; then
|
|
echo "FAIL: snake_case db.Column attribute found (must match column name, no underscores):"
|
|
echo "$SNAKE_COLS"
|
|
echo
|
|
VIOLATIONS=$((VIOLATIONS + 1))
|
|
fi
|
|
|
|
echo "==> Checking for snake_case ForeignKey targets..."
|
|
SNAKE_FK=$(grep -rPn "ForeignKey\(['\"][^'\"]*_[^'\"]*['\"]" "${EXCLUDES[@]}" --include='*.py' . 2>/dev/null || true)
|
|
if [ -n "$SNAKE_FK" ]; then
|
|
echo "FAIL: snake_case ForeignKey target found:"
|
|
echo "$SNAKE_FK"
|
|
echo
|
|
VIOLATIONS=$((VIOLATIONS + 1))
|
|
fi
|
|
|
|
echo "==> Checking for snake_case API params in frontend (DB-mirrored fields)..."
|
|
SNAKE_FE=$(grep -rPn "params\.(machine_id|location_id|vendor_id|type_id|business_unit_id|model_id|status_id|operating_system_id|asset_id|user_id|is_active|is_shopfloor)" "${EXCLUDES[@]}" --include='*.vue' --include='*.js' --include='*.ts' . 2>/dev/null || true)
|
|
if [ -n "$SNAKE_FE" ]; then
|
|
echo "FAIL: snake_case API params in frontend (must match DB column names without underscores):"
|
|
echo "$SNAKE_FE"
|
|
echo
|
|
VIOLATIONS=$((VIOLATIONS + 1))
|
|
fi
|
|
|
|
# ADR-013 Phase 4: a plugin frontend (plugins/<name>/frontend/) may import core
|
|
# via the @/ alias or its own tree (./, ../ within the plugin), never another
|
|
# plugin's tree and never an escaping ../../ into src. Keeps plugin frontends
|
|
# self-contained so a per-site build can drop one cleanly.
|
|
echo "==> Checking for cross-plugin / escaping imports in plugin frontends..."
|
|
if [ -d plugins ]; then
|
|
PLUGIN_FE_IMPORTS=$(grep -rPn "(import|from)\s+['\"]([^'\"]*\.\./\.\./|[^'\"]*/plugins/)" \
|
|
--include='*.vue' --include='*.js' plugins/*/frontend/ 2>/dev/null || true)
|
|
if [ -n "$PLUGIN_FE_IMPORTS" ]; then
|
|
echo "FAIL: plugin frontend imports must use the @/ alias for core, not"
|
|
echo " an escaping ../../ or another plugin's path:"
|
|
echo "$PLUGIN_FE_IMPORTS"
|
|
echo
|
|
VIOLATIONS=$((VIOLATIONS + 1))
|
|
fi
|
|
fi
|
|
|
|
# ADR-015: one site's data does not belong in product code. A site host, a site
|
|
# FQDN or a site network in plugins/ or shopdb/ ships another site a value it
|
|
# cannot see and did not choose. Use a setting with a NEUTRAL default, a
|
|
# site-namespaced directory (scripts/site_imports/<site>/), or seed data.
|
|
#
|
|
# Code generation lives in one module. Seven views were importing qrcode and
|
|
# jsbarcode directly, each with its own margin, width and error correction, and
|
|
# the answers had already drifted - which on a label means one page scans and
|
|
# another does not. frontend/src/utils/codes.js owns that knowledge now; a view
|
|
# passes what is specific to its label and nothing else.
|
|
echo "==> Checking that only the shared module imports the code libraries..."
|
|
CODE_LIB_IMPORTS=$(grep -rn "from ['\"]\(qrcode\|jsbarcode\)['\"]" \
|
|
--include='*.vue' --include='*.js' \
|
|
frontend/src plugins/ 2>/dev/null \
|
|
| grep -v 'frontend/src/utils/codes\.js' \
|
|
| grep -v '\.plugins-staged/' || true)
|
|
if [ -n "$CODE_LIB_IMPORTS" ]; then
|
|
echo "FAIL: import qrcode/jsbarcode through @/utils/codes, not directly:"
|
|
echo "$CODE_LIB_IMPORTS"
|
|
echo
|
|
VIOLATIONS=$((VIOLATIONS + 1))
|
|
fi
|
|
|
|
# ENFORCING. It was report-only while the backlog was worked off, and the hit
|
|
# count then did not move for weeks - a rule that only prints is read as no rule.
|
|
# Set SITE_LITERALS_ENFORCE=0 to drop back to reporting for a local run.
|
|
SITE_LITERALS_ENFORCE=${SITE_LITERALS_ENFORCE:-1}
|
|
|
|
echo "==> Checking for site-specific literals in product code (ADR-015)..."
|
|
# A real site hostname, a site FQDN, a site name or a site network.
|
|
#
|
|
# SCOPE. It used to look at .py/.vue/.js under plugins/ and shopdb/ only, and
|
|
# every literal that actually reached a second site was somewhere else: the
|
|
# PowerShell clients, the installer, the seeds, generated JSON. Case-sensitive
|
|
# too, so Tsgwp00525 passed. Both fixed - the scan is only worth having where
|
|
# the leaks are.
|
|
#
|
|
# Fleet-wide vocabulary (gea-shopfloor-*) is NOT matched: it is overridable
|
|
# through the pctypemap settings and is not one site's data.
|
|
#
|
|
# A line may declare itself deliberate with a trailing `ADR-015-OK: <reason>`
|
|
# marker. That is for an organisation-wide default that is genuinely right for
|
|
# every site and configurable anyway - not for "we will fix it later". The
|
|
# marker makes the claim visible in review; silence would not.
|
|
SITE_PATTERNS='tsgwp00525|\.geaerospace\.net|\bwjs\b|West Jefferson|10\.134\.48\.|10\.48\.249\.'
|
|
SITE_HITS=$(grep -rPni "$SITE_PATTERNS" "${EXCLUDES[@]}" \
|
|
--include='*.py' --include='*.vue' --include='*.js' \
|
|
--include='*.ps1' --include='*.psm1' --include='*.sh' --include='*.iss' \
|
|
--include='*.json' --include='*.html' \
|
|
plugins/ shopdb/ scripts/ deploy/ frontend/src/ tools/ 2>/dev/null \
|
|
| grep -v '/tests\?/' \
|
|
| grep -v 'site_imports/' \
|
|
| grep -v 'installer/bundle/' \
|
|
| grep -v '\.plugins-staged/' \
|
|
| grep -v 'check-naming-and-style\.sh' \
|
|
| grep -v 'export-github\.sh' \
|
|
| grep -v 'ADR-015-OK' || true)
|
|
if [ -n "$SITE_HITS" ]; then
|
|
COUNT=$(echo "$SITE_HITS" | wc -l)
|
|
if [ "$SITE_LITERALS_ENFORCE" = "1" ]; then
|
|
echo "FAIL: $COUNT site-specific literal(s) in product code:"
|
|
echo "$SITE_HITS"
|
|
echo
|
|
VIOLATIONS=$((VIOLATIONS + 1))
|
|
else
|
|
echo " $COUNT site-specific literal(s) found (report only, not failing):"
|
|
echo "$SITE_HITS" | sed 's/^/ /'
|
|
echo " See docs/adr/ADR-015-site-specific-configuration.md"
|
|
echo
|
|
fi
|
|
fi
|
|
|
|
if [ "$VIOLATIONS" -gt 0 ]; then
|
|
echo "=================================================="
|
|
echo "$VIOLATIONS naming/style violation(s) found."
|
|
echo "See CONTRIBUTING.md for the full convention."
|
|
echo "=================================================="
|
|
exit 1
|
|
fi
|
|
|
|
echo "==> All naming/style checks passed."
|
|
exit 0
|