Let config.env preset the project details
Eight optional keys (PROJECT_NAME, PROJECT_DESCRIPTION, PROJECT_VISIBILITY, GITEA_OWNER, USE_GITHUB, GITHUB_OWNER, PROJECT_DIRECTORY, ENABLE_PLAN_GATE) are read and checked with the validators the prompts use. A key that is present counts as set (only the description may be empty); it is not asked and the summary marks it "(from config.env)". An invalid value stops the run before any request and names the key. USE_GITHUB=no skips the GitHub owner and warns about a stray GITHUB_OWNER. The confirmations stay interactive. Keys and an example are documented; tests cover every key. Task: MIL-004#1 Task: MIL-004#2 Task: MIL-004#3 Task: MIL-004#4 Task: MIL-004#5 Closes #27 Closes #28 Closes #29 Closes #30 Closes #31 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
+40
-1
@@ -4,7 +4,7 @@
|
||||
#
|
||||
# Part of create-project.sh: sourced by it, never run on its own.
|
||||
#
|
||||
# Provides: unquote_value, parse_env_file, parse_env_entry, validate_config, validate_credentials, require_github_credentials, warn_if_env_unsafe, load_configuration
|
||||
# Provides: unquote_value, parse_env_file, parse_env_entry, validate_config, check_preset, check_preset_choice, validate_project_presets, validate_credentials, require_github_credentials, warn_if_env_unsafe, load_configuration
|
||||
|
||||
# unquote_value RAW: strip matching quotes (or a trailing " # comment" on an
|
||||
# unquoted value) and return the value in REPLY. Fails on unbalanced quotes.
|
||||
@@ -103,6 +103,45 @@ validate_config() {
|
||||
CONFIG[FRAMEWORK_REPO]="${CONFIG[FRAMEWORK_REPO]:-$DEFAULT_FRAMEWORK_REPO}"
|
||||
is_valid_framework_repo "${CONFIG[FRAMEWORK_REPO]}" ||
|
||||
die "FRAMEWORK_REPO in $CONFIG_FILE must look like OWNER/NAME"
|
||||
validate_project_presets
|
||||
}
|
||||
|
||||
# check_preset KEY VALIDATOR HINT: when KEY is set in config.env its value must
|
||||
# pass VALIDATOR. A key that is present counts as set; only the description
|
||||
# may be empty. The message names the key, never the value.
|
||||
check_preset() {
|
||||
local key="$1" validator="$2" hint="$3"
|
||||
[[ -n ${CONFIG[$key]+set} ]] || return 0
|
||||
if [[ -z ${CONFIG[$key]} && $key != PROJECT_DESCRIPTION ]]; then
|
||||
die "$key in $CONFIG_FILE is empty; remove the line to be asked, or give a value ($hint)"
|
||||
fi
|
||||
"$validator" "${CONFIG[$key]}" ||
|
||||
die "$key in $CONFIG_FILE is not valid: $hint"
|
||||
}
|
||||
|
||||
# check_preset_choice KEY CHOICE...: like check_preset for a fixed list of
|
||||
# words; the value is stored in lower case.
|
||||
check_preset_choice() {
|
||||
local key="$1"
|
||||
shift
|
||||
[[ -n ${CONFIG[$key]+set} ]] || return 0
|
||||
[[ -n ${CONFIG[$key]} ]] ||
|
||||
die "$key in $CONFIG_FILE is empty; remove the line to be asked, or give one of: $*"
|
||||
CONFIG[$key]="${CONFIG[$key],,}"
|
||||
in_list "${CONFIG[$key]}" "$@" ||
|
||||
die "$key in $CONFIG_FILE must be one of: $*"
|
||||
}
|
||||
|
||||
# The optional project details that may be preset in config.env.
|
||||
validate_project_presets() {
|
||||
check_preset PROJECT_NAME is_valid_repo_name "$HINT_REPO_NAME"
|
||||
check_preset PROJECT_DESCRIPTION is_valid_description "$HINT_DESCRIPTION"
|
||||
check_preset_choice PROJECT_VISIBILITY private public
|
||||
check_preset GITEA_OWNER is_valid_gitea_owner "$HINT_GITEA_OWNER"
|
||||
check_preset_choice USE_GITHUB yes no
|
||||
check_preset GITHUB_OWNER is_valid_github_owner "$HINT_GITHUB_OWNER"
|
||||
check_preset PROJECT_DIRECTORY is_valid_directory "$HINT_DIRECTORY"
|
||||
check_preset_choice ENABLE_PLAN_GATE yes no
|
||||
}
|
||||
|
||||
validate_credentials() {
|
||||
|
||||
+12
-1
@@ -19,11 +19,20 @@ readonly DEFAULT_SSH_PORT=10022
|
||||
readonly DEFAULT_FRAMEWORK_REPO="TirSystem/SQA-QC-Framework"
|
||||
readonly AGPL_LICENSE_KEY="AGPL-3.0"
|
||||
readonly DEFAULT_BRANCH="main"
|
||||
# What the prompts and the preset keys in config.env both tell the Maintainer
|
||||
# when a value is refused.
|
||||
readonly HINT_REPO_NAME="use letters, digits, '.', '_' or '-' (at most 100), not ending in .git"
|
||||
readonly HINT_DESCRIPTION="at most $MAX_DESCRIPTION_LENGTH characters and no control characters"
|
||||
readonly HINT_GITEA_OWNER="use letters, digits, '.', '_' or '-' (at most 39)"
|
||||
readonly HINT_GITHUB_OWNER="use letters, digits or '-' (at most 39)"
|
||||
readonly HINT_DIRECTORY="must not be empty, start with '-' or contain control characters"
|
||||
readonly PLAN_STEPS=("GitHub repository" "Gitea repository" "Push mirror"
|
||||
"Local project" "Framework" "Skills and hooks" "Templates")
|
||||
# shellcheck disable=SC2034 # read through namerefs (parse_env_file)
|
||||
readonly CONFIG_KEYS=(GITHUB_API_URL GITHUB_WEB_URL GITEA_URL GITEA_API_URL
|
||||
GITEA_SSH_PORT MIRROR_INTERVAL FRAMEWORK_REPO)
|
||||
GITEA_SSH_PORT MIRROR_INTERVAL FRAMEWORK_REPO
|
||||
PROJECT_NAME PROJECT_DESCRIPTION PROJECT_VISIBILITY GITEA_OWNER USE_GITHUB
|
||||
GITHUB_OWNER PROJECT_DIRECTORY ENABLE_PLAN_GATE)
|
||||
readonly CREDENTIAL_KEYS=(GITHUB_PAT GITHUB_USER GITEA_TOKEN)
|
||||
|
||||
CONFIG_FILE="$PROJECT_ROOT/config.env"
|
||||
@@ -41,6 +50,8 @@ TEMP_FILES=()
|
||||
declare -A CONFIG=()
|
||||
declare -A CREDENTIALS=()
|
||||
declare -A PROJECT=()
|
||||
# Project details that came from config.env instead of a prompt (PRESET[name]=1).
|
||||
declare -A PRESET=()
|
||||
# Facts found by the preflight checks (logins, owner kinds, repository state).
|
||||
declare -A STATE=()
|
||||
# Outcome of each step in PLAN_STEPS, for the final report.
|
||||
|
||||
+56
-24
@@ -4,34 +4,59 @@
|
||||
#
|
||||
# Part of create-project.sh: sourced by it, never run on its own.
|
||||
#
|
||||
# Provides: collect_project_details, yes_no, credential_state, print_summary
|
||||
# Provides: preset_detail, collect_project_details, collect_github_details, source_note, yes_no, credential_state, print_summary
|
||||
|
||||
# preset_detail KEY NAME: a detail set in config.env is used and not asked;
|
||||
# the value is returned in REPLY and marked in PRESET[NAME].
|
||||
preset_detail() {
|
||||
[[ -n ${CONFIG[$1]+set} ]] || return 1
|
||||
REPLY="${CONFIG[$1]}"
|
||||
PRESET[$2]=1
|
||||
}
|
||||
|
||||
# Ask for each project detail, except those set in config.env.
|
||||
collect_project_details() {
|
||||
prompt_value "Repository name" "" is_valid_repo_name \
|
||||
"use letters, digits, '.', '_' or '-' (at most 100), not ending in .git"
|
||||
preset_detail PROJECT_NAME name ||
|
||||
prompt_value "Repository name" "" is_valid_repo_name "$HINT_REPO_NAME"
|
||||
PROJECT[name]="$REPLY"
|
||||
prompt_value "Description (optional)" "" is_valid_description \
|
||||
"at most $MAX_DESCRIPTION_LENGTH characters and no control characters"
|
||||
preset_detail PROJECT_DESCRIPTION description ||
|
||||
prompt_value "Description (optional)" "" is_valid_description "$HINT_DESCRIPTION"
|
||||
PROJECT[description]="$REPLY"
|
||||
prompt_choice "Visibility" private private public
|
||||
preset_detail PROJECT_VISIBILITY visibility ||
|
||||
prompt_choice "Visibility" private private public
|
||||
PROJECT[visibility]="$REPLY"
|
||||
prompt_value "Gitea owner (user or organization)" "" is_valid_gitea_owner \
|
||||
"use letters, digits, '.', '_' or '-' (at most 39)"
|
||||
preset_detail GITEA_OWNER gitea_owner ||
|
||||
prompt_value "Gitea owner (user or organization)" "" is_valid_gitea_owner "$HINT_GITEA_OWNER"
|
||||
PROJECT[gitea_owner]="$REPLY"
|
||||
prompt_yes_no "Also create a GitHub repository (applies the AGPL license)" y
|
||||
collect_github_details
|
||||
preset_detail PROJECT_DIRECTORY directory ||
|
||||
prompt_value "Local directory" "./${PROJECT[name]}" is_valid_directory "$HINT_DIRECTORY"
|
||||
PROJECT[directory]="$REPLY"
|
||||
if preset_detail ENABLE_PLAN_GATE is_plan_gate_enabled; then
|
||||
[[ $REPLY == yes ]] && REPLY=1 || REPLY=0
|
||||
else
|
||||
prompt_yes_no "Enable the plan gate" n
|
||||
fi
|
||||
PROJECT[is_plan_gate_enabled]="$REPLY"
|
||||
}
|
||||
|
||||
# Whether GitHub is used, and its owner. A GITHUB_OWNER set while GitHub is
|
||||
# not used is ignored, with a warning.
|
||||
collect_github_details() {
|
||||
if preset_detail USE_GITHUB has_github; then
|
||||
[[ $REPLY == yes ]] && REPLY=1 || REPLY=0
|
||||
else
|
||||
prompt_yes_no "Also create a GitHub repository (applies the AGPL license)" y
|
||||
fi
|
||||
PROJECT[has_github]="$REPLY"
|
||||
PROJECT[github_owner]=""
|
||||
if ((PROJECT[has_github])); then
|
||||
prompt_value "GitHub owner (user or organization)" \
|
||||
"${CREDENTIALS[GITHUB_USER]:-}" is_valid_github_owner \
|
||||
"use letters, digits or '-' (at most 39)"
|
||||
preset_detail GITHUB_OWNER github_owner ||
|
||||
prompt_value "GitHub owner (user or organization)" "${CREDENTIALS[GITHUB_USER]:-}" is_valid_github_owner "$HINT_GITHUB_OWNER"
|
||||
PROJECT[github_owner]="$REPLY"
|
||||
elif [[ -n ${CONFIG[GITHUB_OWNER]+set} ]]; then
|
||||
warn "GITHUB_OWNER in $CONFIG_FILE is ignored because GitHub is not used"
|
||||
fi
|
||||
prompt_value "Local directory" "./${PROJECT[name]}" is_valid_directory \
|
||||
"must not be empty, start with '-' or contain control characters"
|
||||
PROJECT[directory]="$REPLY"
|
||||
prompt_yes_no "Enable the plan gate" n
|
||||
PROJECT[is_plan_gate_enabled]="$REPLY"
|
||||
}
|
||||
|
||||
yes_no() {
|
||||
@@ -50,20 +75,27 @@ credential_state() {
|
||||
fi
|
||||
}
|
||||
|
||||
# source_note NAME: the marker shown after a value that came from config.env.
|
||||
source_note() {
|
||||
if [[ -n ${PRESET[$1]:-} ]]; then
|
||||
printf ' (from config.env)'
|
||||
fi
|
||||
}
|
||||
|
||||
print_summary() {
|
||||
say ""
|
||||
say "$PROJECT_NAME $VERSION"
|
||||
say "Collected details:"
|
||||
say " Repository : ${PROJECT[name]} (${PROJECT[visibility]})"
|
||||
say " Description : ${PROJECT[description]:-(none)}"
|
||||
say " Gitea : ${CONFIG[GITEA_URL]}/${PROJECT[gitea_owner]}/${PROJECT[name]}"
|
||||
say " Repository : ${PROJECT[name]}$(source_note name) (${PROJECT[visibility]}$(source_note visibility))"
|
||||
say " Description : ${PROJECT[description]:-(none)}$(source_note description)"
|
||||
say " Gitea : ${CONFIG[GITEA_URL]}/${PROJECT[gitea_owner]}/${PROJECT[name]}$(source_note gitea_owner)"
|
||||
if ((PROJECT[has_github])); then
|
||||
say " GitHub : ${CONFIG[GITHUB_WEB_URL]}/${PROJECT[github_owner]}/${PROJECT[name]} (AGPL license applied)"
|
||||
say " GitHub : ${CONFIG[GITHUB_WEB_URL]}/${PROJECT[github_owner]}/${PROJECT[name]} (AGPL license applied)$(source_note github_owner)"
|
||||
else
|
||||
say " GitHub : not used"
|
||||
say " GitHub : not used$(source_note has_github)"
|
||||
fi
|
||||
say " Directory : ${PROJECT[directory]}"
|
||||
say " Plan gate : $(yes_no "${PROJECT[is_plan_gate_enabled]}")"
|
||||
say " Directory : ${PROJECT[directory]}$(source_note directory)"
|
||||
say " Plan gate : $(yes_no "${PROJECT[is_plan_gate_enabled]}")$(source_note is_plan_gate_enabled)"
|
||||
say "Credentials : GITEA_TOKEN $(credential_state GITEA_TOKEN)," \
|
||||
"GITHUB_PAT $(credential_state GITHUB_PAT)"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user