Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ Build the static site using
npm run build
```

### Updating the redirects

Run ` ./generate_redirects.sh ./cards` from the root of this project to update the `_redirects` fils. This will enable netlify to redirect the "naked" URLs to the new nested structure

## Deploying to essentials.xebia.com

TBD
79 changes: 79 additions & 0 deletions _redirects
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Auto-generated by generate_redirects.sh on 2025-08-18T12:21:10Z
# Root: ./cards
# Status: 301
# Slugify FROM: 0
/index / 301
/no-blame-no-mercy /base/collaboration/no-blame-no-mercy 301
/no-broken-windows /base/collaboration/no-broken-windows 301
/pair-programming /base/collaboration/pair-programming 301
/have-fun /base/collaboration/have-fun 301
/team-rhythm /base/collaboration/team-rhythm 301
/build-it-run-it /base/collaboration/build-it-run-it 301
/eliminate-waste /base/collaboration/eliminate-waste 301
/hurt-often /base/collaboration/hurt-often 301
/brutal-transparency /base/collaboration/brutal-transparency 301
/no-broken-builds /base/collaboration/no-broken-builds 301
/one-feature-at-a-time /base/collaboration/one-feature-at-a-time 301
/alone-time /base/collaboration/alone-time 301
/done /base/collaboration/done 301
/the-zone /base/collaboration/the-zone 301
/definition-of-ready /base/collaboration/definition-of-ready 301
/focus-on-flow /base/collaboration/focus-on-flow 301
/make-it-visible /base/collaboration/make-it-visible 301
/timebox /base/collaboration/timebox 301
/code-review /base/collaboration/code-review 301
/what-you-measure /base/collaboration/what-you-measure 301
/team-member-equality /base/collaboration/team-member-equality 301
/done-is-live /base/collaboration/done-is-live 301
/small-increments /base/craftsmanship/small-increments 301
/dry-principle /base/craftsmanship/dry-principle 301
/no-multitasking /base/craftsmanship/no-multitasking 301
/curiosity /base/craftsmanship/curiosity 301
/comment-with-care /base/craftsmanship/comment-with-care 301
/time-for-tech-debt /base/craftsmanship/time-for-tech-debt 301
/dare-to-say-no /base/craftsmanship/dare-to-say-no 301
/no-test-no-bugfix /base/craftsmanship/no-test-no-bugfix 301
/two-minute-rule /base/craftsmanship/two-minute-rule 301
/improve-continuously /base/craftsmanship/improve-continuously 301
/learn-a-new-language /base/craftsmanship/learn-a-new-language 301
/readable-code /base/craftsmanship/readable-code 301
/one-change-at-a-time /base/craftsmanship/one-change-at-a-time 301
/no-museum /base/craftsmanship/no-museum 301
/genchi-genbutsu /base/craftsmanship/genchi-genbutsu 301
/make-it-work-right-fast /base/craftsmanship/make-it-work-right-fast 301
/rich-communication /base/craftsmanship/rich-communication 301
/context-over-habit /base/craftsmanship/context-over-habit 301
/diagnose-before-cure /base/craftsmanship/diagnose-before-cure 301
/clean-build /base/craftsmanship/clean-build 301
/three-strikes /base/craftsmanship/three-strikes 301
/master-your-tools /base/craftsmanship/master-your-tools 301
/tests-are-specs /base/testing/tests-are-specs 301
/test-everything /base/testing/test-everything 301
/independent-tests /base/testing/independent-tests 301
/run-tests-automatically /base/testing/run-tests-automatically 301
/acceptance-criteria /base/testing/acceptance-criteria 301
/testing-is-shared-responsibility /base/testing/testing-is-shared-responsibility 301
/exploratory-testing /base/testing/exploratory-testing 301
/tdd-shapes-design /base/testing/tdd-shapes-design 301
/fail-fast /base/testing/fail-fast 301
/clean-logs /base/testing/clean-logs 301
/test-code-one /base/testing/test-code-one 301
/kiss /base/realisation/kiss 301
/integrate-early /base/realisation/integrate-early 301
/automate-everything /base/realisation/automate-everything 301
/secure-development /base/realisation/secure-development 301
/poutsma-principle /base/realisation/poutsma-principle 301
/shared-design-understanding /base/realisation/shared-design-understanding 301
/thirty-minute-methods /base/realisation/thirty-minute-methods 301
/maximize-cohesion-minimize-coupling /base/realisation/maximize-cohesion-minimize-coupling 301
/apis-are-forever /base/realisation/apis-are-forever 301
/separation-of-concerns /base/realisation/separation-of-concerns 301
/thread-safe /base/realisation/thread-safe 301
/non-functionals /base/realisation/non-functionals 301
/boy-scout-rule /base/realisation/boy-scout-rule 301
/fallacies-distributed-computing /base/realisation/fallacies-distributed-computing 301
/composition-over-inheritance /base/realisation/composition-over-inheritance 301
/hands-off-machine /base/realisation/hands-off-machine 301
/assertions /base/realisation/assertions 301
/focused-interfaces /base/realisation/focused-interfaces 301
/no-anemic-domain-model /base/realisation/no-anemic-domain-model 301
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
127 changes: 127 additions & 0 deletions generate_redirects.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#!/usr/bin/env bash
set -euo pipefail

# Generate a Netlify _redirects file mapping each markdown filename (no extension)
# to its nested path (no extension).
#
# Usage:
# ./generate_redirects.sh [root_dir] [output_file]
#
# Env vars:
# STATUS_CODE HTTP status to use (default: 301)
# SLUGIFY If "1", slugify the FROM path (lowercase, non-alnum -> "-") (default: 0)
# WARN_DUPLICATES If "1", warn when multiple files share the same FROM (default: 1)
#
# Examples:
# ./generate_redirects.sh content ./_redirects
# SLUGIFY=1 STATUS_CODE=302 ./generate_redirects.sh

ROOT="${1:-.}"
OUT="${2:-_redirects}"
STATUS_CODE="${STATUS_CODE:-301}"
SLUGIFY="${SLUGIFY:-0}"
WARN_DUPLICATES="${WARN_DUPLICATES:-1}"

if [ ! -d "$ROOT" ]; then
echo "Error: root directory \"$ROOT\" not found." >&2
exit 1
fi

tmp="$(mktemp)"
trap 'rm -f "$tmp"' EXIT

slugify() {
# lowercase, replace non-alnum with '-', trim leading/trailing '-'
# Note: uses POSIX tools
printf '%s' "$1" \
| tr '[:upper:]' '[:lower:]' \
| sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//'
}

normalize_path() {
# remove duplicate slashes, resolve "./" segments in a simple way
# keep leading slash
# input: path starting with "/"
printf '%s' "$1" \
| sed -E 's#//+#/#g; s#/\./#/#g; s#^/\.?/?#/#'
}

# Write header
{
echo "# Auto-generated by generate_redirects.sh on $(date -u +'%Y-%m-%dT%H:%M:%SZ')"
echo "# Root: $ROOT"
echo "# Status: $STATUS_CODE"
echo "# Slugify FROM: $SLUGIFY"
} > "$OUT"

# Collect raw rules into tmp as tab-separated: FROM <tab> TO <tab> STATUS
# - FROM: "/<filename-no-ext>" (possibly slugified)
# - TO: "/<relative/dir>/<filename-no-ext>" (index.md -> "/<relative/dir>" ; root index.md -> "/")
# Walk the tree safely with NUL delimiters
find "$ROOT" -type f \( -iname '*.md' -o -iname '*.mdx' \) -print0 \
| while IFS= read -r -d '' f; do
rel="${f#"$ROOT"/}"
base="$(basename "$f")"
name="${base%.*}" # filename without extension
dir="$(dirname "$rel")"

from_name="$name"
if [ "$SLUGIFY" = "1" ]; then
from_name="$(slugify "$from_name")"
fi

from="/$from_name"

# Build TO path
if [ "$name" = "index" ]; then
if [ "$dir" = "." ]; then
to="/"
else
to="/$dir"
fi
else
if [ "$dir" = "." ]; then
to="/$name"
else
to="/$dir/$name"
fi
fi

from="$(normalize_path "$from")"
to="$(normalize_path "$to")"

# Skip empty FROM (possible if slugify produced empty)
if [ -z "$from" ] || [ "$from" = "/" ]; then
# Still allow "/index" if name was index and SLUGIFY removed it? We skip to be safe.
continue
fi

# Write tab-separated for robust processing
printf "%s\t%s\t%s\n" "$from" "$to" "$STATUS_CODE" >> "$tmp"
done

# Deduplicate by FROM (keep first); optionally warn about duplicates
awk -F '\t' -v OFS='\t' -v warn="$WARN_DUPLICATES" '
{
from=$1
if (!(from in seen)) {
seen[from]=NR
lines[NR]=$0
order[NR]=from
} else if (warn) {
dups[from]++
}
}
END {
for (i=1; i<=NR; i++) {
if (i in lines) print lines[i]
}
if (warn) {
for (k in dups) {
printf "WARNING: duplicate source \"%s\". Only the first occurrence is kept.\n", k > "/dev/stderr"
}
}
}
' "$tmp" >> "$OUT"

echo "Wrote $(wc -l < "$OUT") lines to $OUT"