diff --git a/README.md b/README.md index 54a48c5..95fcdb5 100644 --- a/README.md +++ b/README.md @@ -59,13 +59,73 @@ src/create-project.sh --help Nothing has to be installed system-wide: the script runs from the checkout and 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 `./`. 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 -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 are accepted, and anything else stops the run with a message that names the key 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 cp config.env.example config.env # service addresses, not secret: set GITEA_URL (and GITEA_API_URL) 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 ``` +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, 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 diff --git a/tests/lib.sh b/tests/lib.sh index dff2e91..80f8764 100644 --- a/tests/lib.sh +++ b/tests/lib.sh @@ -167,7 +167,7 @@ setup_local_remotes() { # first, then the now empty directories from the bottom up. remove_workdir() { 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 {} + fi WORK="" diff --git a/tests/test-launch.sh b/tests/test-launch.sh index dfa1293..883e665 100644 --- a/tests/test-launch.sh +++ b/tests/test-launch.sh @@ -4,7 +4,7 @@ # --config and --env, else ./config.env and ./.env, else the checkout's. # Sourced by run-tests.sh. -# shellcheck disable=SC2016 # snippet and fixture text is literal on purpose +# 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. @@ -53,19 +53,19 @@ test_the_framework_checklists_are_fetched() { 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 git_in + 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 "" "init_framework_submodules '$dir'" + 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 "" "init_framework_submodules '$dir'" + 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" @@ -78,14 +78,15 @@ test_a_failed_qc_fetch_names_the_command_to_run_by_hand() { 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 - printf '[url "file://%s/nowhere/"]\n\tinsteadOf = ssh://git@git.tirsystem.com:10022/\n' "$WORK" >>"$WORK/gitconfig" - GIT_CONFIG_GLOBAL="$WORK/gitconfig" GIT_CONFIG_NOSYSTEM=1 git -C "$dir/framework" config --unset-all submodule.qc.url >/dev/null 2>&1 || true - run_lib "" "init_framework_submodules '$dir'" - if ((STATUS == 0)); then - # The earlier alias still wins in this git; remove the working alias instead. - sed -i '/remote\/"\]/,+1d' "$WORK/gitconfig" - run_lib "" "init_framework_submodules '$dir'" + # 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" @@ -96,7 +97,7 @@ test_a_framework_without_a_submodule_of_its_own_is_not_a_failure() { local dir="$WORK/plain" mkdir -p "$dir" git init -q "$dir" - run_lib "" "init_framework_submodules '$dir'" + run_lib "" "setup_temp_dir; init_framework_submodules '$dir'" assert_status "nothing to fetch" 0 "$STATUS" } @@ -186,7 +187,7 @@ test_the_script_runs_through_a_link_and_creates_the_project_in_the_current_folde make_folder local bin="$WORK/linkbin" mkdir -p "$bin" - ln -s "$CHECKOUT/src/create-project.sh" "$bin/repo-foundry" 2>/dev/null || true + 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 @@ -196,7 +197,7 @@ test_the_script_runs_through_a_link_and_creates_the_project_in_the_current_folde 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" "Config file : $CHECKOUT/config.env (from the checkout)" + 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" } @@ -207,12 +208,12 @@ test_a_link_to_a_link_is_followed() { make_folder local bin="$WORK/linkbin" mkdir -p "$bin" - ln -s "$CHECKOUT/src/create-project.sh" "$bin/first" 2>/dev/null || true + 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" && ln -s first second) + (cd "$bin" && MSYS=winsymlinks:nativestrict ln -s first second) run_from "$bin/second" "$FOLDER" "" --version assert_status "second link" 0 "$STATUS" } diff --git a/tests/test-security.sh b/tests/test-security.sh index 950f546..b5d081a 100644 --- a/tests/test-security.sh +++ b/tests/test-security.sh @@ -201,13 +201,15 @@ test_default_files_are_in_the_project_root() { cp "$WORK/config.env" "$WORK/project/config.env" cp "$WORK/.env" "$WORK/project/.env" STATUS=0 - 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=$? + (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=$? 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" - # 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 - (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=$? assert_status "run from another directory" 0 "$STATUS" }