mirror of
https://github.com/runyanjake/jakeswestcoast.git
synced 2026-06-25 08:04:52 -07:00
Fix linting?
This commit is contained in:
parent
acd95a2033
commit
fc9739c504
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
node_modules
|
||||||
|
build
|
||||||
|
.git
|
||||||
25
Dockerfile
25
Dockerfile
@ -1,19 +1,26 @@
|
|||||||
FROM node:14 AS build
|
# Shared dependency layer, reused by the ci and build targets.
|
||||||
|
FROM node:20-alpine AS deps
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
COPY package.json ./
|
||||||
COPY package*.json /app
|
|
||||||
|
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
|
||||||
COPY . /app
|
# Lint target, used by CI (docker build --target ci).
|
||||||
|
FROM deps AS ci
|
||||||
|
COPY . .
|
||||||
|
RUN npx eslint src --max-warnings=0
|
||||||
|
|
||||||
|
# Production build target: bundles the static site.
|
||||||
|
FROM deps AS build
|
||||||
|
COPY . .
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
|
# Serve the static bundle with nginx.
|
||||||
FROM nginx:alpine
|
FROM nginx:alpine
|
||||||
|
|
||||||
COPY --from=build /app/build /usr/share/nginx/html
|
COPY --from=build /app/build /usr/share/nginx/html
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
# Liveness probe. Use 127.0.0.1 (not localhost): nginx listens IPv4-only and
|
||||||
|
# busybox wget would resolve localhost to ::1 and get connection refused.
|
||||||
|
HEALTHCHECK --interval=5s --timeout=3s --start-period=5s --retries=3 \
|
||||||
|
CMD wget -q -O /dev/null http://127.0.0.1:80/ || exit 1
|
||||||
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
|||||||
127
Jenkinsfile
vendored
127
Jenkinsfile
vendored
@ -2,20 +2,16 @@ pipeline {
|
|||||||
agent any
|
agent any
|
||||||
|
|
||||||
environment {
|
environment {
|
||||||
// Secret: Discord webhook used by the post-build notifier.
|
|
||||||
DISCORD_WEBHOOK = credentials('discord-pws-builds-channel-webhook')
|
DISCORD_WEBHOOK = credentials('discord-pws-builds-channel-webhook')
|
||||||
|
|
||||||
// Deployment identity / topology (non-secret, declared here for auditability).
|
COMPOSE_SERVICE = 'app'
|
||||||
COMPOSE_PROJECT_NAME = 'jakeswestcoast'
|
CONTAINER_NAME = 'jakeswestcoast'
|
||||||
APP_CONTAINER = 'jakeswestcoast'
|
}
|
||||||
DEPLOY_NETWORK = 'traefik'
|
|
||||||
|
|
||||||
// Clean, pinned image used to run static checks in isolation.
|
options {
|
||||||
LINT_IMAGE = 'node:18-alpine'
|
timestamps()
|
||||||
|
disableConcurrentBuilds()
|
||||||
// Health/smoke probing (served internally on port 80, reached via the container).
|
timeout(time: 30, unit: 'MINUTES')
|
||||||
HEALTH_URL = 'http://localhost:80/'
|
|
||||||
SMOKE_MARKER = 'id="root"'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stages {
|
stages {
|
||||||
@ -28,65 +24,63 @@ pipeline {
|
|||||||
stage('Preflight') {
|
stage('Preflight') {
|
||||||
steps {
|
steps {
|
||||||
sh '''
|
sh '''
|
||||||
set -e
|
set -eu
|
||||||
[ -n "$DISCORD_WEBHOOK" ] || { echo "ERROR: DISCORD_WEBHOOK credential is missing."; exit 1; }
|
: "${DISCORD_WEBHOOK:?required credential discord-pws-builds-channel-webhook is missing}"
|
||||||
docker network inspect "$DEPLOY_NETWORK" >/dev/null 2>&1 \
|
for f in Dockerfile package.json docker-compose.yml; do
|
||||||
|| { echo "ERROR: external network '$DEPLOY_NETWORK' does not exist."; exit 1; }
|
[ -f "$f" ] || { echo "ERROR: required file '$f' not found at repo root" >&2; exit 1; }
|
||||||
docker compose config -q \
|
done
|
||||||
|| { echo "ERROR: docker-compose configuration is invalid."; exit 1; }
|
docker compose config -q
|
||||||
'''
|
'''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('Lint & Type-check') {
|
stage('Lint & Type-check') {
|
||||||
steps {
|
steps {
|
||||||
// Run checks in a throwaway container so host state can't mask errors.
|
// Static checks run inside the image's ci target; nothing touches the agent.
|
||||||
sh '''
|
sh 'docker build --target ci -t ${CONTAINER_NAME}-ci:${BUILD_NUMBER} .'
|
||||||
set -e
|
|
||||||
docker run --rm -v "$WORKSPACE:/app" -w /app "$LINT_IMAGE" \
|
|
||||||
sh -c "npm install --no-audit --no-fund && npx eslint src/" \
|
|
||||||
|| { echo "ERROR: static checks failed."; exit 1; }
|
|
||||||
'''
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('Teardown') {
|
stage('Teardown') {
|
||||||
steps {
|
steps {
|
||||||
sh '''
|
sh '''
|
||||||
set -e
|
set -eu
|
||||||
docker compose down --remove-orphans \
|
docker compose down --remove-orphans || true
|
||||||
|| { echo "ERROR: failed to tear down the previous deployment."; exit 1; }
|
# container_name is fixed, so a stale container can survive "down"
|
||||||
|
# and then block "up" with a name conflict; reap it explicitly.
|
||||||
|
docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true
|
||||||
'''
|
'''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('Build & Deploy') {
|
stage('Build & Deploy') {
|
||||||
steps {
|
steps {
|
||||||
sh '''
|
// No build-time secrets: the React build consumes only static assets.
|
||||||
set -e
|
sh 'docker compose up -d --build'
|
||||||
docker compose up -d --build \
|
|
||||||
|| { echo "ERROR: build and deploy failed."; exit 1; }
|
|
||||||
'''
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('Health Check') {
|
stage('Health Check') {
|
||||||
steps {
|
steps {
|
||||||
// Poll until the container serves, failing fast if it exits or never becomes ready.
|
|
||||||
sh '''
|
sh '''
|
||||||
set -e
|
set -eu
|
||||||
for i in $(seq 1 20); do
|
cid="$(docker compose ps -q "$COMPOSE_SERVICE")"
|
||||||
running=$(docker inspect -f '{{.State.Running}}' "$APP_CONTAINER" 2>/dev/null || echo "false")
|
[ -n "$cid" ] || { echo "ERROR: $COMPOSE_SERVICE container not found" >&2; exit 1; }
|
||||||
[ "$running" = "true" ] || { echo "ERROR: container '$APP_CONTAINER' is not running."; exit 1; }
|
|
||||||
if docker exec "$APP_CONTAINER" wget -q -O /dev/null "$HEALTH_URL"; then
|
# Wait for the image's HEALTHCHECK to report healthy, failing fast on terminal states.
|
||||||
echo "App is live."
|
deadline=$(( $(date +%s) + 90 ))
|
||||||
exit 0
|
while :; do
|
||||||
fi
|
status="$(docker inspect -f '{{.State.Status}}' "$cid")"
|
||||||
echo "Waiting for app to become ready ($i)..."
|
health="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$cid")"
|
||||||
sleep 3
|
[ "$status" = "running" ] && [ "$health" = "healthy" ] && break
|
||||||
|
[ "$health" = "unhealthy" ] && { echo "ERROR: $COMPOSE_SERVICE reported unhealthy" >&2; exit 1; }
|
||||||
|
case "$status" in
|
||||||
|
exited|dead) echo "ERROR: $COMPOSE_SERVICE container $status before becoming healthy" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
[ "$(date +%s)" -ge "$deadline" ] && { echo "ERROR: timed out waiting for healthy (status=$status, health=$health)" >&2; exit 1; }
|
||||||
|
sleep 2
|
||||||
done
|
done
|
||||||
echo "ERROR: app did not become healthy in time."
|
echo "$COMPOSE_SERVICE healthy"
|
||||||
exit 1
|
|
||||||
'''
|
'''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,12 +88,14 @@ pipeline {
|
|||||||
stage('Smoke Test') {
|
stage('Smoke Test') {
|
||||||
steps {
|
steps {
|
||||||
sh '''
|
sh '''
|
||||||
set -e
|
set -eu
|
||||||
body=$(docker exec "$APP_CONTAINER" wget -q -O- "$HEALTH_URL") \
|
cid="$(docker compose ps -q "$COMPOSE_SERVICE")"
|
||||||
|| { echo "ERROR: smoke request failed."; exit 1; }
|
[ -n "$cid" ] || { echo "ERROR: $COMPOSE_SERVICE container not found" >&2; exit 1; }
|
||||||
echo "$body" | grep -q "$SMOKE_MARKER" \
|
|
||||||
|| { echo "ERROR: smoke test did not find expected content ('$SMOKE_MARKER')."; exit 1; }
|
# Real request from inside the container: GET / must serve the SPA shell.
|
||||||
echo "Smoke test passed."
|
body="$(docker exec "$cid" wget -q -O - http://127.0.0.1:80/)" || { echo "ERROR: GET / did not return a successful response" >&2; exit 1; }
|
||||||
|
echo "$body" | grep -q 'id="root"' || { echo "ERROR: GET / response missing expected app markup" >&2; exit 1; }
|
||||||
|
echo "smoke test passed"
|
||||||
'''
|
'''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,24 +105,26 @@ pipeline {
|
|||||||
always {
|
always {
|
||||||
script {
|
script {
|
||||||
def result = currentBuild.currentResult
|
def result = currentBuild.currentResult
|
||||||
def emoji = result == 'SUCCESS' ? ':green_circle:' : (result == 'FAILURE' ? ':red_circle:' : ':yellow_circle:')
|
def emoji = result == 'SUCCESS' ? ':green_circle:' :
|
||||||
def branch = env.GIT_BRANCH ?: env.BRANCH_NAME ?: 'Main/Manual'
|
result == 'FAILURE' ? ':red_circle:' : ':yellow_circle:'
|
||||||
def duration = currentBuild.durationString.replace(' and no weeks', '').replace(' and counting', '')
|
|
||||||
|
|
||||||
def commitLines = []
|
def branch = env.BRANCH_NAME ?: env.GIT_BRANCH ?: 'Main/Manual'
|
||||||
for (changeSet in currentBuild.changeSets) {
|
|
||||||
for (commit in changeSet.items) {
|
def duration = currentBuild.durationString
|
||||||
commitLines.add("> ${commit.msg} (by *${commit.author.fullName}*)")
|
.replace(' and no weeks', '')
|
||||||
}
|
.replace(' and counting', '')
|
||||||
|
|
||||||
|
def commits = currentBuild.changeSets.collectMany { set ->
|
||||||
|
set.items.collect { "> ${it.msg} (by *${it.author.fullName}*)" }
|
||||||
}
|
}
|
||||||
def commits = commitLines ? commitLines.join('\n') : 'No recent changes detected.'
|
def commitText = commits ? commits.join('\n') : 'No recent changes detected.'
|
||||||
|
|
||||||
def discordDescription = """**Status:** ${emoji} ${result}
|
def discordDescription = """**Status:** ${emoji} ${result}
|
||||||
**Branch:** `${branch}`
|
**Branch:** `${branch}`
|
||||||
**Duration:** :stopwatch: ${duration}
|
**Duration:** :stopwatch: ${duration}
|
||||||
|
|
||||||
**Commits:**
|
**Commits:**
|
||||||
${commits}"""
|
${commitText}"""
|
||||||
|
|
||||||
discordSend(
|
discordSend(
|
||||||
webhookURL: env.DISCORD_WEBHOOK,
|
webhookURL: env.DISCORD_WEBHOOK,
|
||||||
@ -137,12 +135,13 @@ ${commits}"""
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
failure {
|
failure {
|
||||||
sh '''
|
sh '''
|
||||||
echo "===== docker compose ps ====="
|
echo "=== docker compose ps ==="
|
||||||
docker compose ps || true
|
docker compose ps || true
|
||||||
echo "===== recent logs ====="
|
echo "=== recent logs ==="
|
||||||
docker compose logs --no-color --tail=200 || true
|
docker compose logs --tail=200 || true
|
||||||
'''
|
'''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,10 @@
|
|||||||
"extends": [
|
"extends": [
|
||||||
"react-app",
|
"react-app",
|
||||||
"react-app/jest"
|
"react-app/jest"
|
||||||
]
|
],
|
||||||
|
"env": {
|
||||||
|
"es2021": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user