Add the GitHub and Gitea steps and split the script into library files

Dry run by default: the script reads from both hosts (tokens, owners,
names, license, SSH) and prints a plan; --apply creates the repositories
and the Gitea -> GitHub push mirror after a final yes. Choosing GitHub
applies the AGPL-3.0 license to the Gitea repository. A failed step is
reported with what exists and how to continue; nothing is ever deleted.

create-project.sh is now the entry point; the work lives in src/lib/, one
responsibility per file. .gitignore gets !src/lib (the Python template
ignores any lib/ folder). Tests grow to 599 checks, with a stub curl and
ssh, and guards for the file structure.

Task: MIL-002#1
Task: MIL-002#2
Task: MIL-002#3
Task: MIL-002#4
Task: MIL-002#5
Task: MIL-002#6
Refs #9
Refs #10
Refs #11
Refs #12
Refs #13
Refs #14

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-05 15:29:10 +08:00
co-authored by Claude Sonnet 5.5
parent 903c948d85
commit 736cdb42fc
30 changed files with 2269 additions and 668 deletions
+43
View File
@@ -0,0 +1,43 @@
# shellcheck shell=bash
# shellcheck disable=SC2004,SC2034,SC2154 # shared state and arrays are declared in constants.sh
# output.sh - Messages for the user: output, warnings, errors, and redaction of secrets.
#
# Part of create-project.sh: sourced by it, never run on its own.
#
# Provides: redact, say, warn, die, on_error
# redact TEXT: print TEXT with every known secret value replaced.
redact() {
local text="$1" secret
for secret in "${SECRET_VALUES[@]}"; do
if [[ -n $secret ]]; then
text="${text//"$secret"/[redacted]}"
fi
done
printf '%s' "$text"
}
say() {
printf '%s\n' "$(redact "$*")"
}
warn() {
printf 'warning: %s\n' "$(redact "$*")" >&2
}
# die [--code N] MESSAGE: print "error: MESSAGE" and exit (default code 1).
die() {
local code=$EXIT_FAILURE
if [[ ${1:-} == --code ]]; then
code="$2"
shift 2
fi
printf 'error: %s\n' "$(redact "$*")" >&2
exit "$code"
}
# on_error LINE: report an unexpected failure without echoing the command,
# because a command line could contain a value that must stay private.
on_error() {
printf 'error: unexpected failure near line %s of %s\n' "$1" "${0##*/}" >&2
}