After the repositories and the mirror, the script now creates the local project: the directory, git on main, credential-free origin (and github) remotes, and the license history from Gitea. Then it adds the framework as a submodule over SSH, installs its skills and git hooks once (plan gate optional), and copies the AGENTS.md and artifact registry templates. Nothing is overwritten without a yes: an existing directory, core.hooksPath, AGENTS.md or docs/artifact-registry.md each ask first (default no). Without SSH the framework steps can only be skipped, after a yes. No commit is made in the new project. New optional config key FRAMEWORK_REPO. New library files git.sh, localproject.sh and framework.sh. Tests run real git against local bare repositories that stand in for Gitea and the framework, with a private git configuration (769 checks). The README is now the full guide. Task: MIL-003#1 Task: MIL-003#2 Task: MIL-003#3 Task: MIL-003#4 Task: MIL-003#5 Refs #15 Refs #16 Refs #17 Refs #18 Refs #19 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
188 lines
6.8 KiB
Bash
188 lines
6.8 KiB
Bash
#!/usr/bin/env bash
|
|
# create-project.sh - set up a new project on Gitea (and optionally GitHub).
|
|
#
|
|
# Purpose
|
|
# RepoFoundry creates a Gitea repository, optionally an empty GitHub
|
|
# repository with a Gitea -> GitHub push mirror, and a local project with
|
|
# the SQA-QC-Framework. It validates the configuration and credentials,
|
|
# asks for the project details, checks both hosts with read-only requests
|
|
# (tokens, owners, names, license, SSH) and, with --apply, creates the
|
|
# repositories and the mirror, then the local project: its directory, git
|
|
# repository, remotes (no credential in any address), the framework as a
|
|
# submodule, the framework's skills and git hooks (and the plan gate if
|
|
# chosen) and its templates. Choosing GitHub also applies the AGPL-3.0
|
|
# license to the Gitea repository. No commit is made in the new project.
|
|
#
|
|
# Dry run by default
|
|
# Without --apply the script only reads from GitHub and Gitea (GET
|
|
# requests) and prints what it would create. With --apply it prints the plan
|
|
# and asks for a final yes before it creates anything. Nothing is ever
|
|
# deleted: if a step fails, the script reports what exists and how to
|
|
# continue, and a repeated run offers to reuse the empty repositories.
|
|
#
|
|
# Usage
|
|
# create-project.sh [--apply] [--config FILE] [--env FILE]
|
|
# create-project.sh --help | --version
|
|
#
|
|
# Options
|
|
# --apply create the repositories and the mirror (after a final yes)
|
|
# --config FILE service addresses (default: config.env in the project root)
|
|
# --env FILE credentials (default: .env in the project root)
|
|
# -h, --help show this help
|
|
# --version show the version
|
|
#
|
|
# Files (parsed, never sourced)
|
|
# config.env GITHUB_API_URL, GITHUB_WEB_URL, GITEA_URL, GITEA_API_URL and
|
|
# the optional GITEA_SSH_PORT (default 10022), MIRROR_INTERVAL
|
|
# (default 10m0s) and FRAMEWORK_REPO (default
|
|
# TirSystem/SQA-QC-Framework, the submodule's OWNER/NAME)
|
|
# .env GITHUB_PAT, GITHUB_USER, GITEA_TOKEN
|
|
#
|
|
# Environment
|
|
# REPOFOUNDRY_NAME project name used in messages (default: RepoFoundry)
|
|
# REPOFOUNDRY_SYNC_WAIT seconds to wait before reading the first mirror
|
|
# sync result (default: 3)
|
|
# TMPDIR where the private temporary directory is created
|
|
#
|
|
# Requires
|
|
# bash 4.4 or later, git, curl, mktemp; jq and ssh are optional (jq is used
|
|
# for JSON when present; ssh is used for the Gitea SSH test).
|
|
# Also the base tools sed, grep, head, tr, sleep, find, cp, mkdir, chmod, env, rm,
|
|
# rmdir and uname, and
|
|
# stat (GNU "stat -c" or BSD "stat -f"; only used outside Windows).
|
|
#
|
|
# Implements
|
|
# MIL-001 tasks 1 to 6, MIL-002 tasks 1 to 5 and MIL-003 tasks 1 to 4
|
|
# (issues #3 to #13 and #15 to #18), user stories US-001.01 to US-001.03,
|
|
# UC-001 steps 1 to 10; see docs/. Deviation from the request: its second
|
|
# GITEA_URL key is named GITEA_API_URL.
|
|
#
|
|
# Tracing
|
|
# set -x is switched off while the script runs, because a trace would print
|
|
# every secret the script handles.
|
|
#
|
|
# Structure
|
|
# This file is the entry point. The work is split by responsibility into
|
|
# the files in lib/ next to it (one job per file, see the first lines of
|
|
# each file): constants, output, temp, util, validate, config, tools, json,
|
|
# http, api, prompts, project, hosts, preflight, steps, plan, repositories,
|
|
# mirror, git, localproject, framework, apply and cli. The files are loaded
|
|
# from this directory only.
|
|
#
|
|
# Exit codes
|
|
# 0 success (or a dry run, or a "no" at the final question), 1 a failed
|
|
# check, bad input or a failed step, 2 a usage error.
|
|
set -Eeuo pipefail
|
|
|
|
if [[ $- == *x* ]]; then
|
|
set +x
|
|
printf 'warning: tracing (set -x) is disabled because it would print secrets\n' >&2
|
|
fi
|
|
|
|
if ((BASH_VERSINFO[0] < 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] < 4))); then
|
|
printf 'error: bash 4.4 or later is required (found %s)\n' "$BASH_VERSION" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Where this script lives; the library files and the project root are found
|
|
# from here, never from the current directory.
|
|
readonly SCRIPT_FILE="${BASH_SOURCE[0]}"
|
|
case "${BASH_SOURCE[0]}" in
|
|
*/*) script_path_dir="${BASH_SOURCE[0]%/*}" ;;
|
|
*) script_path_dir="." ;;
|
|
esac
|
|
SCRIPT_DIR="$(cd "$script_path_dir" && pwd)"
|
|
readonly SCRIPT_DIR
|
|
unset script_path_dir
|
|
# The script lives in src/; the configuration files live one level up, in
|
|
# the project root, next to config.env.example and .env.example.
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
readonly PROJECT_ROOT
|
|
|
|
# shellcheck source=lib/constants.sh
|
|
source "$SCRIPT_DIR/lib/constants.sh"
|
|
# shellcheck source=lib/output.sh
|
|
source "$SCRIPT_DIR/lib/output.sh"
|
|
# shellcheck source=lib/temp.sh
|
|
source "$SCRIPT_DIR/lib/temp.sh"
|
|
# shellcheck source=lib/util.sh
|
|
source "$SCRIPT_DIR/lib/util.sh"
|
|
# shellcheck source=lib/validate.sh
|
|
source "$SCRIPT_DIR/lib/validate.sh"
|
|
# shellcheck source=lib/config.sh
|
|
source "$SCRIPT_DIR/lib/config.sh"
|
|
# shellcheck source=lib/tools.sh
|
|
source "$SCRIPT_DIR/lib/tools.sh"
|
|
# shellcheck source=lib/json.sh
|
|
source "$SCRIPT_DIR/lib/json.sh"
|
|
# shellcheck source=lib/http.sh
|
|
source "$SCRIPT_DIR/lib/http.sh"
|
|
# shellcheck source=lib/api.sh
|
|
source "$SCRIPT_DIR/lib/api.sh"
|
|
# shellcheck source=lib/prompts.sh
|
|
source "$SCRIPT_DIR/lib/prompts.sh"
|
|
# shellcheck source=lib/project.sh
|
|
source "$SCRIPT_DIR/lib/project.sh"
|
|
# shellcheck source=lib/hosts.sh
|
|
source "$SCRIPT_DIR/lib/hosts.sh"
|
|
# shellcheck source=lib/preflight.sh
|
|
source "$SCRIPT_DIR/lib/preflight.sh"
|
|
# shellcheck source=lib/steps.sh
|
|
source "$SCRIPT_DIR/lib/steps.sh"
|
|
# shellcheck source=lib/plan.sh
|
|
source "$SCRIPT_DIR/lib/plan.sh"
|
|
# shellcheck source=lib/repositories.sh
|
|
source "$SCRIPT_DIR/lib/repositories.sh"
|
|
# shellcheck source=lib/mirror.sh
|
|
source "$SCRIPT_DIR/lib/mirror.sh"
|
|
# shellcheck source=lib/git.sh
|
|
source "$SCRIPT_DIR/lib/git.sh"
|
|
# shellcheck source=lib/localproject.sh
|
|
source "$SCRIPT_DIR/lib/localproject.sh"
|
|
# shellcheck source=lib/framework.sh
|
|
source "$SCRIPT_DIR/lib/framework.sh"
|
|
# shellcheck source=lib/apply.sh
|
|
source "$SCRIPT_DIR/lib/apply.sh"
|
|
# shellcheck source=lib/cli.sh
|
|
source "$SCRIPT_DIR/lib/cli.sh"
|
|
|
|
# finish runs on every exit: it reports what a run that started creating
|
|
# things did or did not do, then removes the temporary files. It keeps the
|
|
# exit status of the run.
|
|
finish() {
|
|
local code=$?
|
|
if ((IS_CREATION_STARTED)); then
|
|
report_outcome "$code"
|
|
fi
|
|
cleanup
|
|
}
|
|
|
|
main() {
|
|
trap 'on_error "$LINENO"' ERR
|
|
trap finish EXIT
|
|
is_valid_repo_name "$PROJECT_NAME" ||
|
|
die "REPOFOUNDRY_NAME is not a valid project name"
|
|
parse_args "$@"
|
|
check_tools
|
|
setup_temp_dir
|
|
load_configuration
|
|
collect_project_details
|
|
if ((PROJECT[has_github])); then
|
|
require_github_credentials
|
|
fi
|
|
init_steps
|
|
print_summary
|
|
run_preflight
|
|
print_plan
|
|
if ((IS_APPLY)); then
|
|
apply_plan
|
|
else
|
|
say ""
|
|
say "Dry run: nothing was created. Run again with --apply to create it."
|
|
fi
|
|
}
|
|
|
|
if [[ ${BASH_SOURCE[0]} == "$0" ]]; then
|
|
main "$@"
|
|
fi
|