Implement MIL-007: qc submodules fetched, start through a link, default configuration files #53

Merged
Tirsvad merged 2 commits from mil-007-impl into mil-006-license 2026-10-07 08:17:23 +02:00
10 changed files with 402 additions and 21 deletions
+66 -1
View File
@@ -59,13 +59,73 @@ src/create-project.sh --help
Nothing has to be installed system-wide: the script runs from the checkout and Nothing has to be installed system-wide: the script runs from the checkout and
loads its own files from `src/lib/`. loads its own files from `src/lib/`.
### Run it from the folder where the project is to be created
The new project is created under the folder you start the script in: the
default directory is `./<repository name>`. Go to the folder that should hold
the project, then start the script from there, by its path or by a global
command (below):
```bash
cd ~/work # the folder that will hold my-app
~/src/RepoFoundry/src/create-project.sh # creates ~/work/my-app
```
### Make it a global command
Put a link to the script in a folder that is on your `PATH`. The script
follows the link to the checkout, so it still finds its own files there.
Linux, macOS and Git Bash on Windows (run this once, from the checkout):
```bash
mkdir -p ~/bin
ln -s "$PWD/src/create-project.sh" ~/bin/repo-foundry
```
If `~/bin` is not on your `PATH` yet, add it and open a new shell:
```bash
echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc # ~/.zshrc on macOS
```
Check that it works, from any folder:
```bash
cd ~/work
repo-foundry --version # prints the name and version
repo-foundry # a dry run that creates nothing
```
On Windows, Git Bash makes a *copy* instead of a link unless symbolic links
are allowed (Developer Mode, or an administrator shell). Either allow them and
run `export MSYS=winsymlinks:nativestrict` before the `ln -s`, or use an alias
in `~/.bashrc`, which works because the script finds its own folder:
```bash
alias repo-foundry='bash /c/Users/me/RepoFoundry/src/create-project.sh'
```
## Configuration ## Configuration
The script reads two plain files from the project root. They are **parsed, The script reads two plain files. They are **parsed,
never executed** (`source` is not used): only `KEY=VALUE` lines with known keys never executed** (`source` is not used): only `KEY=VALUE` lines with known keys
are accepted, and anything else stops the run with a message that names the key are accepted, and anything else stops the run with a message that names the key
and the line, never the value. and the line, never the value.
Each file is chosen on its own, in this order:
1. the file named with `--config` or `--env`;
2. `./config.env` or `./.env` in the folder you start the script in;
3. `config.env` or `.env` in the checkout (next to `src/`).
The script names the files it uses before it contacts any host. A file taken
from the folder you started in is also confirmed: the script shows the file
names and the Gitea address and asks for a yes (default no) before the first
request, because a `config.env` in a folder you do not control could point
Gitea at another host and so send your token there. A file you name with
`--config` or `--env`, or the checkout's own, is not asked about.
```bash ```bash
cp config.env.example config.env # service addresses, not secret: set GITEA_URL (and GITEA_API_URL) cp config.env.example config.env # service addresses, not secret: set GITEA_URL (and GITEA_API_URL)
cp .env.example .env # credentials: keep private cp .env.example .env # credentials: keep private
@@ -151,6 +211,11 @@ src/create-project.sh --apply # creates everything after a final yes
src/create-project.sh --config /path/to/config.env --env /path/to/.env src/create-project.sh --config /path/to/config.env --env /path/to/.env
``` ```
With a global command (see [Installation](#installation)) the same commands are
`repo-foundry`, `repo-foundry --apply` and so on, started from the folder that
should hold the project. Without `--config` and `--env` the files are looked
for as described under [Configuration](#configuration).
The script asks for, in this order: repository name, description, visibility, The script asks for, in this order: repository name, description, visibility,
Gitea owner, whether to also create a GitHub repository (and its owner), the Gitea owner, whether to also create a GitHub repository (and its owner), the
local directory and whether to enable the plan gate (a detail set in local directory and whether to enable the plan gate (a detail set in
@@ -9,8 +9,8 @@
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Task 4 renamed so its issue title is unique (the sync matches issues by title) | [be759e3] | | 2026-10-07 | Deprecated | Jens Tirsvad Nielsen | S02 | Task 4 renamed so its issue title is unique (the sync matches issues by title) | [be759e3] |
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | The configuration files default to the working folder's, then the checkout's, confirmed and named (deliverable 3, criteria 9 and 10, tasks 2 to 4) | [0ab5006] | | 2026-10-07 | Accepted | Jens Tirsvad Nielsen | S02 | The configuration files default to the working folder's, then the checkout's, confirmed and named (deliverable 3, criteria 9 and 10, tasks 2 to 4) | [0ab5006] |
--- ---
+31 -9
View File
@@ -86,18 +86,39 @@ if ((BASH_VERSINFO[0] < 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] < 4)));
fi fi
# Where this script lives; the library files and the project root are found # Where this script lives; the library files and the project root are found
# from here, never from the current directory. # from here, never from the current directory. A link to the script (a
readonly SCRIPT_FILE="${BASH_SOURCE[0]}" # command in a folder on PATH) is followed to the real file, however many
case "${BASH_SOURCE[0]}" in # links lie on the way; readlink without -f exists on Linux, macOS and Git
*/*) script_path_dir="${BASH_SOURCE[0]%/*}" ;; # Bash alike.
script_path="${BASH_SOURCE[0]}"
script_link_count=0
while [[ -L $script_path ]]; do
((++script_link_count <= 40)) || {
printf 'error: too many links when following %s\n' "${BASH_SOURCE[0]}" >&2
exit 1
}
script_link_target="$(readlink -- "$script_path")"
case "$script_link_target" in
/*) script_path="$script_link_target" ;;
*)
case "$script_path" in
*/*) script_path="${script_path%/*}/$script_link_target" ;;
*) script_path="$script_link_target" ;;
esac
;;
esac
done
readonly SCRIPT_FILE="$script_path"
case "$script_path" in
*/*) script_path_dir="${script_path%/*}" ;;
*) script_path_dir="." ;; *) script_path_dir="." ;;
esac esac
SCRIPT_DIR="$(cd "$script_path_dir" && pwd)" SCRIPT_DIR="$(cd -P "$script_path_dir" && pwd -P)"
readonly SCRIPT_DIR readonly SCRIPT_DIR
unset script_path_dir unset script_path script_path_dir script_link_count script_link_target
# The script lives in src/; the configuration files live one level up, in # The script lives in src/; the checkout is one level up, next to
# the project root, next to config.env.example and .env.example. # config.env.example and .env.example.
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" PROJECT_ROOT="$(cd -P "$SCRIPT_DIR/.." && pwd -P)"
readonly PROJECT_ROOT readonly PROJECT_ROOT
# shellcheck source=lib/constants.sh # shellcheck source=lib/constants.sh
@@ -163,6 +184,7 @@ main() {
trap finish EXIT trap finish EXIT
is_valid_repo_name "$PROJECT_NAME" || is_valid_repo_name "$PROJECT_NAME" ||
die "REPOFOUNDRY_NAME is not a valid project name" die "REPOFOUNDRY_NAME is not a valid project name"
WORKING_FOLDER="$(pwd -P)"
parse_args "$@" parse_args "$@"
check_tools check_tools
setup_temp_dir setup_temp_dir
+2
View File
@@ -9,6 +9,8 @@
usage() { usage() {
cat <<EOF cat <<EOF
Usage: ${0##*/} [--apply] [--config FILE] [--env FILE] Usage: ${0##*/} [--apply] [--config FILE] [--env FILE]
Without --config and --env, ./config.env and ./.env in the current folder
are read, then the ones in the checkout.
${0##*/} --help | --version ${0##*/} --help | --version
EOF EOF
} }
+47
View File
@@ -198,9 +198,56 @@ warn_if_env_unsafe() {
fi fi
} }
# locate_config_file NAME FLAG FILE_VAR ORIGIN_VAR: the file named with FLAG;
# otherwise ./NAME in the working folder; otherwise NAME in the checkout.
# The origin is "named", "folder" or "checkout".
locate_config_file() {
local name="$1" flag="$2"
local -n file_ref="$3" origin_ref="$4"
if [[ -n $file_ref ]]; then
origin_ref="named"
elif [[ $WORKING_FOLDER != "$PROJECT_ROOT" && -f $WORKING_FOLDER/$name ]]; then
file_ref="$WORKING_FOLDER/$name"
origin_ref="folder"
elif [[ -f $PROJECT_ROOT/$name ]]; then
file_ref="$PROJECT_ROOT/$name"
origin_ref="checkout"
else
die "$name is not named with $flag and is in neither the working folder ($WORKING_FOLDER) nor the checkout ($PROJECT_ROOT); name it with $flag FILE"
fi
}
# origin_words ORIGIN: how the origin of a configuration file reads.
origin_words() {
case "$1" in
named) printf 'named on the command line' ;;
folder) printf 'from the working folder' ;;
*) printf 'from the checkout' ;;
esac
}
# confirm_folder_files: a file taken from the working folder may point the
# Gitea address elsewhere and so send the token there, so it is named with
# the address and needs a yes (default no) before the first request.
confirm_folder_files() {
local files=()
[[ $CONFIG_ORIGIN == folder ]] && files+=("$CONFIG_FILE")
[[ $ENV_ORIGIN == folder ]] && files+=("$ENV_FILE")
((${#files[@]} > 0)) || return 0
say "The working folder supplies: ${files[*]}"
say "Gitea would be ${CONFIG[GITEA_URL]}; the token from the credentials file is sent there."
prompt_yes_no "Use ${files[*]}" n
((REPLY)) || die "stopped before any request: choose the files with --config and --env, or run from the checkout"
}
load_configuration() { load_configuration() {
locate_config_file config.env --config CONFIG_FILE CONFIG_ORIGIN
locate_config_file .env --env ENV_FILE ENV_ORIGIN
say "Config file : $CONFIG_FILE ($(origin_words "$CONFIG_ORIGIN"))"
say "Credentials file: $ENV_FILE ($(origin_words "$ENV_ORIGIN"))"
parse_env_file "$CONFIG_FILE" CONFIG_KEYS CONFIG parse_env_file "$CONFIG_FILE" CONFIG_KEYS CONFIG
validate_config validate_config
confirm_folder_files
parse_env_file "$ENV_FILE" CREDENTIAL_KEYS CREDENTIALS parse_env_file "$ENV_FILE" CREDENTIAL_KEYS CREDENTIALS
validate_credentials validate_credentials
warn_if_env_unsafe "$ENV_FILE" warn_if_env_unsafe "$ENV_FILE"
+8 -2
View File
@@ -37,8 +37,14 @@ readonly CONFIG_KEYS=(GITHUB_API_URL GITHUB_WEB_URL GITEA_URL GITEA_API_URL
GITHUB_OWNER PROJECT_DIRECTORY ENABLE_PLAN_GATE PROJECT_LICENSE) GITHUB_OWNER PROJECT_DIRECTORY ENABLE_PLAN_GATE PROJECT_LICENSE)
readonly CREDENTIAL_KEYS=(GITHUB_PAT GITHUB_USER GITEA_TOKEN) readonly CREDENTIAL_KEYS=(GITHUB_PAT GITHUB_USER GITEA_TOKEN)
CONFIG_FILE="$PROJECT_ROOT/config.env" # The configuration files: named by --config and --env, or chosen by
ENV_FILE="$PROJECT_ROOT/.env" # locate_config_file. The origin is named, folder or checkout.
CONFIG_FILE=""
ENV_FILE=""
CONFIG_ORIGIN=""
ENV_ORIGIN=""
# The folder the Maintainer started the script in.
WORKING_FOLDER=""
TMP_DIR="" TMP_DIR=""
HAS_JQ=0 HAS_JQ=0
HTTP_STATUS=0 HTTP_STATUS=0
+15 -1
View File
@@ -4,7 +4,7 @@
# #
# Part of create-project.sh: sourced by it, never run on its own. # Part of create-project.sh: sourced by it, never run on its own.
# #
# Provides: is_framework_skipped, add_framework, run_framework_script, install_skills, install_hooks, install_framework, copy_template, copy_templates # Provides: is_framework_skipped, init_framework_submodules, add_framework, run_framework_script, install_skills, install_hooks, install_framework, copy_template, copy_templates
# is_framework_skipped: succeed when the framework steps are left out because # is_framework_skipped: succeed when the framework steps are left out because
# SSH to Gitea is not available (the Maintainer agreed to that). # SSH to Gitea is not available (the Maintainer agreed to that).
@@ -12,6 +12,18 @@ is_framework_skipped() {
[[ ${STATE[skip_framework]:-0} == 1 ]] [[ ${STATE[skip_framework]:-0} == 1 ]]
} }
# init_framework_submodules DIR: fetch the submodules the framework holds
# itself (the qc checklists). Without a submodule of its own this changes
# nothing. A failure names the command to run by hand.
init_framework_submodules() {
local dir="$1" err
make_temp_file
err="$REPLY"
if ! git_project "$dir" submodule update -q --init --recursive 2>"$err"; then
die "the framework was added but git could not fetch its own submodules (the qc checklists). Run in $dir: git submodule update --init --recursive. Git said: $(head -n 2 "$err" | tr '\n' ' ')"
fi
}
# add_framework: git submodule add of the framework as "framework". SSH is # add_framework: git submodule add of the framework as "framework". SSH is
# needed for it; a failure says how to test the access. # needed for it; a failure says how to test the access.
add_framework() { add_framework() {
@@ -27,6 +39,7 @@ add_framework() {
if [[ $existing != "$url" ]]; then if [[ $existing != "$url" ]]; then
die "'framework' already exists in $dir and is not the framework submodule ($url)" die "'framework' already exists in $dir and is not the framework submodule ($url)"
fi fi
init_framework_submodules "$dir"
finish_step "$label" "reused" "$url (already a submodule)" finish_step "$label" "reused" "$url (already a submodule)"
return 0 return 0
fi fi
@@ -35,6 +48,7 @@ add_framework() {
if ! git_project "$dir" submodule add -q "$url" framework 2>"$err"; then if ! git_project "$dir" submodule add -q "$url" framework 2>"$err"; then
die "git could not add the framework from $url. Check the SSH access first: ssh -p ${CONFIG[GITEA_SSH_PORT]} -T git@$(gitea_host). Git said: $(head -n 2 "$err" | tr '\n' ' ')" die "git could not add the framework from $url. Check the SSH access first: ssh -p ${CONFIG[GITEA_SSH_PORT]} -T git@$(gitea_host). Git said: $(head -n 2 "$err" | tr '\n' ' ')"
fi fi
init_framework_submodules "$dir"
finish_step "$label" "created" "$url" finish_step "$label" "created" "$url"
} }
+6 -2
View File
@@ -120,12 +120,15 @@ write_gitconfig() {
insteadOf = https://git.example.test/ insteadOf = https://git.example.test/
[url "file://$WORK/remote/"] [url "file://$WORK/remote/"]
insteadOf = ssh://git@git.example.test:10022/ insteadOf = ssh://git@git.example.test:10022/
[url "file://$WORK/remote/"]
insteadOf = ssh://git@git.tirsystem.com:10022/
EOF EOF
} }
# ensure_shared_remotes: build, once per run of the suite, the local bare # ensure_shared_remotes: build, once per run of the suite, the local bare
# repositories that stand in for Gitea (TirSystem/my-app.git, holding the # repositories that stand in for Gitea (TirSystem/my-app.git, holding the
# license commit) and for the framework (a copy of the real one). # license commit), for the framework and for the checklists the framework
# holds as its own submodule (copies of the real ones).
ensure_shared_remotes() { ensure_shared_remotes() {
if [[ -n $SHARED_REMOTES && -d $SHARED_REMOTES ]]; then if [[ -n $SHARED_REMOTES && -d $SHARED_REMOTES ]]; then
return 0 return 0
@@ -133,6 +136,7 @@ ensure_shared_remotes() {
SHARED_REMOTES="$(mktemp -d "${TMPDIR:-/tmp}/repofoundry-remotes.XXXXXX")" SHARED_REMOTES="$(mktemp -d "${TMPDIR:-/tmp}/repofoundry-remotes.XXXXXX")"
mkdir -p "$SHARED_REMOTES/TirSystem" mkdir -p "$SHARED_REMOTES/TirSystem"
git clone -q --bare "$REPO_ROOT/framework" "$SHARED_REMOTES/TirSystem/SQA-QC-Framework.git" git clone -q --bare "$REPO_ROOT/framework" "$SHARED_REMOTES/TirSystem/SQA-QC-Framework.git"
git clone -q --bare "$REPO_ROOT/framework/qc" "$SHARED_REMOTES/TirSystem/SQA-QC-Checklists.git"
git init -q --bare "$SHARED_REMOTES/TirSystem/my-app.git" git init -q --bare "$SHARED_REMOTES/TirSystem/my-app.git"
git init -q "$SHARED_REMOTES/seed" git init -q "$SHARED_REMOTES/seed"
git -C "$SHARED_REMOTES/seed" symbolic-ref HEAD refs/heads/main git -C "$SHARED_REMOTES/seed" symbolic-ref HEAD refs/heads/main
@@ -163,7 +167,7 @@ setup_local_remotes() {
# first, then the now empty directories from the bottom up. # first, then the now empty directories from the bottom up.
remove_workdir() { remove_workdir() {
if [[ -n $WORK && -d $WORK ]]; then if [[ -n $WORK && -d $WORK ]]; then
find "$WORK" \( -type f -o -type p \) -delete find "$WORK" \( -type f -o -type p -o -type l \) -delete
find "$WORK" -depth -type d -exec rmdir {} + find "$WORK" -depth -type d -exec rmdir {} +
fi fi
WORK="" WORK=""
+219
View File
@@ -0,0 +1,219 @@
#!/usr/bin/env bash
# test-launch.sh - tests for MIL-007: the framework's own submodules (qc) are
# fetched, the script starts through a link, and the configuration files are
# --config and --env, else ./config.env and ./.env, else the checkout's.
# Sourced by run-tests.sh.
# shellcheck disable=SC2016,SC2153 # snippet and fixture text is literal on purpose; SCRIPT comes from lib.sh
# make_checkout: a copy of the script's own files with config.env and .env
# beside them, standing in for the checkout; the path is in CHECKOUT.
make_checkout() {
CHECKOUT="$(cd "$WORK" && pwd -P)/checkout"
mkdir -p "$CHECKOUT"
cp -R "$SRC_DIR" "$CHECKOUT/src"
cp "$WORK/config.env" "$CHECKOUT/config.env"
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$CHECKOUT/.env"
}
# make_folder: an empty working folder; the path is in FOLDER.
make_folder() {
FOLDER="$(cd "$WORK" && pwd -P)/folder"
mkdir -p "$FOLDER"
}
# run_from SCRIPT_PATH DIR INPUT ARGS...: run the script with DIR as the
# current folder.
run_from() {
local script="$1" dir="$2" input="$3"
shift 3
STATUS=0
(cd "$dir" && PATH="$WORK/bin:$PATH" STUB_DIR="$WORK" TMPDIR="$WORK/tmp" \
REPOFOUNDRY_SYNC_WAIT=0 GIT_CONFIG_GLOBAL="$WORK/gitconfig" GIT_CONFIG_NOSYSTEM=1 \
"$BASH" "$script" "$@" <<<"$input" >"$WORK/out.txt" 2>"$WORK/err.txt") ||
STATUS=$?
OUT="$(cat "$WORK/out.txt")"
ERR="$(cat "$WORK/err.txt")"
}
# --------------------------------------------------- the framework's qc
test_the_framework_checklists_are_fetched() {
setup_hosts
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$WORK/.env"
local dir="$WORK/project" answers
printf -v answers 'my-app\n\n\nTirSystem\nn\n%s\nn\n' "$dir"
run_apply "$answers"$'y\n'
assert_status "apply" 0 "$STATUS"
assert_file_exists "the framework" "$dir/framework/README.md"
assert_file_exists "qc is filled" "$dir/framework/qc/qc-business-case.md"
assert_eq "no submodule is left uninitialised" "" "$(GIT_CONFIG_GLOBAL="$WORK/gitconfig" git -C "$dir" submodule status --recursive | grep '^-' || true)"
}
test_an_empty_qc_is_filled_by_a_second_run_and_a_complete_one_is_not_changed() {
setup_hosts
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$WORK/.env"
local dir="$WORK/project" answers
printf -v answers 'my-app\n\n\nTirSystem\nn\n%s\nn\n' "$dir"
run_apply "$answers"$'y\n'
assert_status "first run" 0 "$STATUS"
git_in() { GIT_CONFIG_GLOBAL="$WORK/gitconfig" GIT_CONFIG_NOSYSTEM=1 git -C "$dir/framework" "$@"; }
git_in submodule deinit -f qc >/dev/null 2>&1
assert_file_missing "qc emptied" "$dir/framework/qc/qc-business-case.md"
run_lib "" "setup_temp_dir; init_framework_submodules '$dir'"
assert_status "repair" 0 "$STATUS"
assert_file_exists "qc filled again" "$dir/framework/qc/qc-business-case.md"
local before after
before="$(git_in status --porcelain)"
run_lib "" "setup_temp_dir; init_framework_submodules '$dir'"
after="$(git_in status --porcelain)"
assert_status "second call" 0 "$STATUS"
assert_eq "nothing else changed" "$before" "$after"
}
test_a_failed_qc_fetch_names_the_command_to_run_by_hand() {
setup_hosts
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$WORK/.env"
local dir="$WORK/project" answers
printf -v answers 'my-app\n\n\nTirSystem\nn\n%s\nn\n' "$dir"
run_apply "$answers"$'y\n'
GIT_CONFIG_GLOBAL="$WORK/gitconfig" GIT_CONFIG_NOSYSTEM=1 git -C "$dir/framework" submodule deinit -f qc >/dev/null 2>&1
# Drop the cached copy of the checklists and refuse local fetches, so they
# cannot be fetched again.
local cache="$dir/.git/modules/framework/modules/qc"
if [[ -d $cache ]]; then
find "$cache" \( -type f -o -type l \) -delete
find "$cache" -depth -type d -exec rmdir {} +
fi
printf '[protocol "file"]\n\tallow = never\n' >>"$WORK/gitconfig"
run_lib "" "setup_temp_dir; init_framework_submodules '$dir'"
assert_status "fetch fails" 1 "$STATUS"
assert_contains "the command" "$ERR" "git submodule update --init --recursive"
assert_not_contains "no token" "$ERR" "$FAKE_GITEA_TOKEN"
}
test_a_framework_without_a_submodule_of_its_own_is_not_a_failure() {
setup_hosts
local dir="$WORK/plain"
mkdir -p "$dir"
git init -q "$dir"
run_lib "" "setup_temp_dir; init_framework_submodules '$dir'"
assert_status "nothing to fetch" 0 "$STATUS"
}
# ------------------------------------------------ the configuration files
test_files_in_the_working_folder_are_used_after_a_yes() {
setup_hosts
make_checkout
make_folder
cp "$WORK/config.env" "$FOLDER/config.env"
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$FOLDER/.env"
run_from "$SCRIPT" "$FOLDER" $'y\n'"$ANSWERS_GITEA_ONLY"
assert_status "folder files" 0 "$STATUS"
assert_contains "config named" "$OUT" "Config file : $FOLDER/config.env (from the working folder)"
assert_contains "credentials named" "$OUT" "Credentials file: $FOLDER/.env (from the working folder)"
assert_contains "the address is named" "$OUT" "Gitea would be https://git.example.test"
assert_contains "asked" "$ERR" "Use $FOLDER/config.env $FOLDER/.env (y/n) [n]"
}
test_files_in_the_working_folder_are_not_used_without_a_yes() {
setup_hosts
make_checkout
make_folder
cp "$WORK/config.env" "$FOLDER/config.env"
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$FOLDER/.env"
rm -f "$WORK/curl.calls"
run_from "$SCRIPT" "$FOLDER" $'\n'"$ANSWERS_GITEA_ONLY"
assert_status "default no" 1 "$STATUS"
assert_contains "stopped" "$ERR" "stopped before any request"
assert_eq "no request to any host" "" "$(calls)"
}
test_named_files_win_and_are_not_confirmed() {
setup_hosts
make_checkout
make_folder
cp "$WORK/config.env" "$FOLDER/config.env"
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$FOLDER/.env"
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$WORK/.env"
run_from "$SCRIPT" "$FOLDER" "$ANSWERS_GITEA_ONLY" --config "$WORK/config.env" --env "$WORK/.env"
assert_status "named files" 0 "$STATUS"
assert_contains "named" "$OUT" "Config file : $WORK/config.env (named on the command line)"
assert_not_contains "no confirmation" "$OUT" "The working folder supplies"
}
test_the_checkouts_files_are_used_when_the_folder_has_none() {
setup_hosts
make_checkout
make_folder
run_from "$CHECKOUT/src/create-project.sh" "$FOLDER" "$ANSWERS_GITEA_ONLY"
assert_status "checkout files" 0 "$STATUS"
assert_contains "config named" "$OUT" "Config file : $CHECKOUT/config.env (from the checkout)"
assert_contains "credentials named" "$OUT" "Credentials file: $CHECKOUT/.env (from the checkout)"
assert_not_contains "no confirmation" "$OUT" "The working folder supplies"
}
test_each_file_is_chosen_on_its_own() {
setup_hosts
make_checkout
make_folder
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$FOLDER/.env"
run_from "$CHECKOUT/src/create-project.sh" "$FOLDER" $'y\n'"$ANSWERS_GITEA_ONLY"
assert_status "mixed" 0 "$STATUS"
assert_contains "config from the checkout" "$OUT" "Config file : $CHECKOUT/config.env (from the checkout)"
assert_contains "credentials from the folder" "$OUT" "Credentials file: $FOLDER/.env (from the working folder)"
assert_contains "asked about the folder file only" "$ERR" "Use $FOLDER/.env (y/n) [n]"
}
test_files_found_nowhere_stop_before_any_request_and_name_both_places() {
setup_hosts
make_checkout
make_folder
rm -f "$CHECKOUT/.env" "$WORK/curl.calls"
run_from "$CHECKOUT/src/create-project.sh" "$FOLDER" "$ANSWERS_GITEA_ONLY"
assert_status "no .env anywhere" 1 "$STATUS"
assert_contains "working folder named" "$ERR" "working folder ($FOLDER)"
assert_contains "checkout named" "$ERR" "checkout ($CHECKOUT)"
assert_contains "the option" "$ERR" "--env FILE"
assert_eq "no request to any host" "" "$(calls)"
}
# ----------------------------------------------------------- a link to it
test_the_script_runs_through_a_link_and_creates_the_project_in_the_current_folder() {
setup_hosts
make_checkout
make_folder
local bin="$WORK/linkbin"
mkdir -p "$bin"
MSYS=winsymlinks:nativestrict ln -s "$CHECKOUT/src/create-project.sh" "$bin/repo-foundry" 2>/dev/null || true
if [[ ! -L $bin/repo-foundry ]]; then
printf 'skipped: this shell cannot make symbolic links\n'
return 0
fi
run_from "$bin/repo-foundry" "$FOLDER" "" --version
assert_status "version through the link" 0 "$STATUS"
assert_contains "found its files" "$OUT" "RepoFoundry"
run_from "$bin/repo-foundry" "$FOLDER" "$ANSWERS_GITEA_ONLY"$'y\n' --apply
assert_status "apply through the link" 0 "$STATUS"
assert_contains "the checkout's files" "$OUT" "/checkout/config.env (from the checkout)"
assert_file_exists "the project is in the current folder" "$FOLDER/my-app/.git"
assert_file_missing "not in the checkout" "$CHECKOUT/my-app"
}
test_a_link_to_a_link_is_followed() {
setup_hosts
make_checkout
make_folder
local bin="$WORK/linkbin"
mkdir -p "$bin"
MSYS=winsymlinks:nativestrict ln -s "$CHECKOUT/src/create-project.sh" "$bin/first" 2>/dev/null || true
[[ -L $bin/first ]] || {
printf 'skipped: this shell cannot make symbolic links\n'
return 0
}
(cd "$bin" && MSYS=winsymlinks:nativestrict ln -s first second)
run_from "$bin/second" "$FOLDER" "" --version
assert_status "second link" 0 "$STATUS"
}
+6 -4
View File
@@ -201,13 +201,15 @@ test_default_files_are_in_the_project_root() {
cp "$WORK/config.env" "$WORK/project/config.env" cp "$WORK/config.env" "$WORK/project/config.env"
cp "$WORK/.env" "$WORK/project/.env" cp "$WORK/.env" "$WORK/project/.env"
STATUS=0 STATUS=0
PATH="$WORK/bin:$PATH" STUB_DIR="$WORK" TMPDIR="$WORK/tmp" "$BASH" "$WORK/project/src/create-project.sh" \ (cd "$WORK/project" && PATH="$WORK/bin:$PATH" STUB_DIR="$WORK" TMPDIR="$WORK/tmp" "$BASH" "$WORK/project/src/create-project.sh" \
<<<"$ANSWERS_GITHUB" >"$WORK/out.txt" 2>"$WORK/err.txt" || STATUS=$? <<<"$ANSWERS_GITHUB" >"$WORK/out.txt" 2>"$WORK/err.txt") || STATUS=$?
assert_status "run with the default files" 0 "$STATUS" assert_status "run with the default files" 0 "$STATUS"
assert_contains "found config.env in the project root" "$(cat "$WORK/out.txt")" "https://git.example.test/TirSystem/my-app" assert_contains "found config.env in the project root" "$(cat "$WORK/out.txt")" "https://git.example.test/TirSystem/my-app"
# Run from another directory: the defaults follow the script, not the cwd. # Run from another folder that holds no files of its own: the checkout's
# files are used; the defaults follow the script, not the current folder.
mkdir -p "$WORK/elsewhere"
STATUS=0 STATUS=0
(cd "$WORK" && PATH="$WORK/bin:$PATH" STUB_DIR="$WORK" TMPDIR="$WORK/tmp" "$BASH" "$WORK/project/src/create-project.sh" \ (cd "$WORK/elsewhere" && PATH="$WORK/bin:$PATH" STUB_DIR="$WORK" TMPDIR="$WORK/tmp" "$BASH" "$WORK/project/src/create-project.sh" \
<<<"$ANSWERS_GITEA_ONLY" >"$WORK/out.txt" 2>"$WORK/err.txt") || STATUS=$? <<<"$ANSWERS_GITEA_ONLY" >"$WORK/out.txt" 2>"$WORK/err.txt") || STATUS=$?
assert_status "run from another directory" 0 "$STATUS" assert_status "run from another directory" 0 "$STATUS"
} }