Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe6e5a4ba0 | ||
|
|
ea7e25b65d | ||
|
|
aca4eed377 | ||
|
|
05a7159c18 |
+5
-2
@@ -1,6 +1,9 @@
|
||||
# RepoFoundry credentials. Placeholders only: never put a real value in this
|
||||
# file or commit one.
|
||||
#
|
||||
# Everything in this file is optional: a credential that is missing here is
|
||||
# asked for when the script runs (the token is not echoed).
|
||||
#
|
||||
# Copy this file to .env, fill in the values and keep it private
|
||||
# (chmod 600 .env on Linux and macOS). .env is ignored by git. The file is
|
||||
# read as plain KEY=VALUE lines and never executed. Values may be wrapped in
|
||||
@@ -13,7 +16,7 @@
|
||||
GITHUB_PAT=
|
||||
|
||||
########################################
|
||||
# Secrets for workframe
|
||||
# Secrets for framework
|
||||
########################################
|
||||
|
||||
# GitHub account the token belongs to. It identifies who authenticates; it is
|
||||
@@ -21,6 +24,6 @@ GITHUB_PAT=
|
||||
# belong to an organization.
|
||||
GITHUB_USER=
|
||||
|
||||
# Gitea access token (required). It needs permission to create repositories
|
||||
# Gitea access token. It needs permission to create repositories
|
||||
# for the chosen owner and to manage the repository's push mirror.
|
||||
GITEA_TOKEN=
|
||||
|
||||
@@ -59,73 +59,13 @@ 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 `./<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
|
||||
|
||||
The script reads two plain files. They are **parsed,
|
||||
The script reads two plain files from the project root. 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
|
||||
@@ -163,7 +103,6 @@ A detail that is set is used and not asked; the summary marks it with
|
||||
| `GITHUB_OWNER` | GitHub user or organization | letters, digits, `-`; used only when GitHub is used |
|
||||
| `PROJECT_DIRECTORY` | local directory | not empty, not starting with `-` |
|
||||
| `ENABLE_PLAN_GATE` | enable the plan gate | `yes` or `no` |
|
||||
| `PROJECT_LICENSE` | license of the project | a Gitea license key (letters, digits, `.`, `+`, `-`; at most 64), such as `AGPL-3.0` or `MIT`, or `none` |
|
||||
|
||||
- A key that is present counts as set, even when its value is empty. Only
|
||||
`PROJECT_DESCRIPTION` may be empty (no description); an empty value for any
|
||||
@@ -172,12 +111,7 @@ A detail that is set is used and not asked; the summary marks it with
|
||||
key. The script never falls back to asking for it.
|
||||
- `USE_GITHUB=no` skips the GitHub owner and every GitHub step; a
|
||||
`GITHUB_OWNER` set at the same time is ignored, with a warning.
|
||||
- `PROJECT_LICENSE` is never asked. When set, that license is put on the Gitea
|
||||
repository with or without GitHub, and `none` means no license. When absent,
|
||||
AGPL-3.0 is applied only if GitHub is used **and** the project is public;
|
||||
a private project, or one without GitHub, gets no license. The Gitea server
|
||||
must offer the license, or the run stops before anything is created.
|
||||
- Only these nine details can be set. The confirmations stay questions that
|
||||
- Only these eight details can be set. The confirmations stay questions that
|
||||
default to no: create now, reusing an existing repository, an existing
|
||||
directory, `core.hooksPath` and replacing a template file.
|
||||
- These keys are accepted in `config.env` only, never in `.env`.
|
||||
@@ -185,7 +119,7 @@ A detail that is set is used and not asked; the summary marks it with
|
||||
value there. Put a description that contains ` #` in double quotes, for
|
||||
example `PROJECT_DESCRIPTION="Tool for #mirrors"`.
|
||||
|
||||
With all of them set, a run asks only the confirmations:
|
||||
With all eight set, a run asks only the confirmations:
|
||||
|
||||
```bash
|
||||
src/create-project.sh --apply # asks only "Create these now (y/n) [n]"
|
||||
@@ -195,14 +129,40 @@ src/create-project.sh --apply # asks only "Create these now (y/n) [n]"
|
||||
|
||||
| Key | Meaning |
|
||||
| --- | --- |
|
||||
| `GITEA_TOKEN` | Gitea access token (required) |
|
||||
| `GITEA_TOKEN` | Gitea access token |
|
||||
| `GITHUB_PAT` | GitHub personal access token (only when you choose GitHub) |
|
||||
| `GITHUB_USER` | the GitHub account the token belongs to; only a default for the owner prompt |
|
||||
| `GITHUB_USER` | the GitHub account the token belongs to (only when you choose GitHub) |
|
||||
|
||||
`.env` is ignored by git. The script warns if it is readable by other users or
|
||||
not ignored by git. See [Token permissions](#token-permissions) for what each
|
||||
token needs.
|
||||
|
||||
`.env` is optional, and so is each key in it. A credential that is not
|
||||
provided (the file is missing, the key is absent or its value is empty) is
|
||||
asked for: the Gitea token at the start, the GitHub token and account name once
|
||||
you choose GitHub. A token is read without echo and checked like one read from
|
||||
`.env`; a refused value is asked again and never shown. If input ends before a
|
||||
valid value is entered, the run stops before any request to a host.
|
||||
|
||||
### The project's own `.env`
|
||||
|
||||
When the project exists, the script asks whether to create a `.env` in it
|
||||
(default no). On a yes the file holds only the credentials the project needs:
|
||||
`GITEA_TOKEN`, and `GITHUB_PAT` and `GITHUB_USER` when you chose GitHub, as read
|
||||
from your `.env` or typed.
|
||||
|
||||
- The file is created readable by you only (mode 600), never readable by
|
||||
others even for a moment, and is never written by anything else.
|
||||
- Git ignores it: the script adds `.env` to `.git/info/exclude` of the new
|
||||
project. No tracked file changes and nothing is committed.
|
||||
- An existing `.env` in the project is never replaced without a second yes, and
|
||||
a `.env` that git already tracks is never written.
|
||||
- The summary names the keys, never the values.
|
||||
|
||||
This is the one place the script writes a token to disk. It is plain text: keep
|
||||
the project directory private, do not copy the file around, and say no if you
|
||||
do not need it. Tokens are written nowhere else.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
@@ -211,11 +171,6 @@ 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
|
||||
@@ -224,8 +179,8 @@ with read-only requests and prints a plan:
|
||||
|
||||
```text
|
||||
Plan:
|
||||
Gitea repository : create (public) with the AGPL-3.0 license (default: GitHub and a public project) https://git.example.org/Team/my-app
|
||||
GitHub repository : create (public), empty https://github.com/acme/my-app
|
||||
Gitea repository : create (private) with the AGPL-3.0 license https://git.example.org/Team/my-app
|
||||
GitHub repository : create (private), empty https://github.com/acme/my-app
|
||||
Push mirror : Gitea -> GitHub every 10m0s
|
||||
Local project : create ./my-app (new directory), git on main, no commit
|
||||
Local origin : will use SSH (the SSH test passed)
|
||||
@@ -238,7 +193,7 @@ Without `--apply` that is all that happens. With `--apply` the script asks
|
||||
"Create these now" (default no) and then creates, in this order:
|
||||
|
||||
1. the GitHub repository (empty), if chosen;
|
||||
2. the Gitea repository (with the license that applies: `PROJECT_LICENSE`, or AGPL-3.0 for a public project with GitHub);
|
||||
2. the Gitea repository (with the AGPL-3.0 license if GitHub was chosen);
|
||||
3. the push mirror Gitea -> GitHub, and a request for its first sync;
|
||||
4. the local directory, `git init` on `main`, the `origin` remote (and `github`
|
||||
if chosen), and, if the Gitea repository holds the license commit, that
|
||||
@@ -252,11 +207,10 @@ hooks refuse commits on `main`.
|
||||
|
||||
### Choices
|
||||
|
||||
- **GitHub or not.** Choosing GitHub sets up the mirror. For a public project it
|
||||
also applies the AGPL-3.0 license to the Gitea repository (so it is not
|
||||
empty), unless `PROJECT_LICENSE` says otherwise. A private project with
|
||||
GitHub gets no license by default. Without GitHub the Gitea repository has
|
||||
no license unless `PROJECT_LICENSE` sets one, and `GITHUB_PAT` is not needed.
|
||||
- **GitHub or not.** Choosing GitHub also applies the AGPL-3.0 license to the
|
||||
Gitea repository (so it is not empty) and sets up the mirror. Without GitHub
|
||||
the Gitea repository is empty and has no license, and `GITHUB_PAT` is not
|
||||
needed.
|
||||
- **Owners.** The Gitea owner and the GitHub owner are chosen separately and
|
||||
may be a user or an organization. `GITHUB_USER` is only the suggested default
|
||||
for the GitHub owner prompt; it identifies who authenticates.
|
||||
@@ -336,7 +290,9 @@ script stops before it creates anything when a preflight check is refused.
|
||||
## Security decisions
|
||||
|
||||
- **Tokens never appear** in output, logs, remote URLs, `.git/config`,
|
||||
`.gitmodules`, command lines or leftover files. They go to `curl` through a
|
||||
`.gitmodules`, command lines or leftover files, and are written to disk only
|
||||
in the new project's own `.env`, after a yes (see
|
||||
[The project's own `.env`](#the-projects-own-env)). They go to `curl` through a
|
||||
private configuration file that is removed right after the request, and to
|
||||
`git` (HTTPS fetch only) through a `GIT_ASKPASS` helper and the environment
|
||||
of that one command. Output is filtered, so even a server message that echoes
|
||||
@@ -440,11 +396,13 @@ file. The files are loaded from that directory only, by a fixed path.
|
||||
| `json.sh` | the little JSON the script reads and writes |
|
||||
| `http.sh` | the one place that runs `curl`; tokens stay off the command line |
|
||||
| `api.sh` | GitHub and Gitea API calls and reporting a refused call |
|
||||
| `prompts.sh` | interactive questions with validation |
|
||||
| `prompts.sh` | interactive questions with validation (secrets are read without echo) |
|
||||
| `credentials.sh` | asking for a credential that `.env` does not provide |
|
||||
| `project.sh` | the project details: asking for them and showing them |
|
||||
| `hosts.sh` | names, links and remote addresses of the repositories |
|
||||
| `preflight.sh` | read-only checks of both hosts |
|
||||
| `steps.sh` | the outcome of each step and the final report |
|
||||
| `envfile.sh` | the new project's own `.env`: created private, ignored by git, never replaced without a yes |
|
||||
| `plan.sh` | printing what the script is about to do |
|
||||
| `repositories.sh` | creating the GitHub and Gitea repositories |
|
||||
| `mirror.sh` | the Gitea to GitHub push mirror |
|
||||
|
||||
@@ -46,12 +46,6 @@ GITEA_API_URL=https://<your gitea instance>/api/v1
|
||||
#PROJECT_DIRECTORY=./my-project
|
||||
#ENABLE_PLAN_GATE=no # yes or no
|
||||
|
||||
# Optional. The license of the project, as a Gitea license key (AGPL-3.0, MIT,
|
||||
# ...), or none for no license. It is never asked. When absent, AGPL-3.0 is
|
||||
# applied only if GitHub is used and the project is public; otherwise there
|
||||
# is no license. The Gitea server must offer the license.
|
||||
#PROJECT_LICENSE=AGPL-3.0
|
||||
|
||||
# Optional. OWNER/NAME of the SQA-QC-Framework repository on the Gitea server;
|
||||
# it is added to the new project as a submodule over SSH.
|
||||
# Default: TirSystem/SQA-QC-Framework.
|
||||
|
||||
@@ -24,7 +24,7 @@ document of a type. `Primary File` may contain a glob (e.g.
|
||||
| DCD | Design Class Diagram | docs/dcd.md | 004 |
|
||||
| DICT | Domain Dictionary (PO and IT terms) | docs/dictionary.md | 002 |
|
||||
| UCD | Use Case Diagram | docs/use-case-diagram.md | 002 |
|
||||
| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 030 |
|
||||
| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 029 |
|
||||
| TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 |
|
||||
|
||||
## Languages
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
## Version History
|
||||
| Date | Status | Author | Reviewer | Change | Commit |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Objectives 2, 5 and 10, two scope items and success criterion 10: the AGPL-3.0 default needs GitHub and a public project; the framework's own submodules (qc) are fetched | [1cd27f7] |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Added objective 11 (global command, project created in the current folder), a scope item and success criterion 11 | [1cd27f7] |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Objective 11, scope item and criterion 11: the configuration files default to the working folder's, then the checkout's | [0ab5006] |
|
||||
|
||||
---
|
||||
|
||||
@@ -44,7 +44,7 @@ One repeatable, reviewed procedure gives every new project the same secure basel
|
||||
8. Let the Maintainer preset the project details in `config.env`, so that a detail that is set there is not asked again.
|
||||
9. Ask for a credential that is not provided in `.env` (`GITEA_TOKEN`, `GITHUB_PAT`, `GITHUB_USER`) and, when the Maintainer agrees, create a `.env` file with the credentials the new project needs.
|
||||
10. Let the Maintainer set the project's license in `config.env` (`PROJECT_LICENSE`), independent of the GitHub choice, or set `none` for no license.
|
||||
11. Let the Maintainer start the script by name from the folder where the project is to be created, through a command link in a folder on `PATH`, using the `config.env` and `.env` in that folder, or the checkout's when it has none.
|
||||
11. Let the Maintainer start the script by name from the folder where the project is to be created, through a command link in a folder on `PATH`.
|
||||
|
||||
## Scope
|
||||
|
||||
@@ -58,7 +58,7 @@ One repeatable, reviewed procedure gives every new project the same secure basel
|
||||
- Asking for a credential that `.env` does not provide, and creating the new project's own `.env` (owner-only, ignored by git, never overwritten without a yes).
|
||||
- A project license set in `config.env` (`PROJECT_LICENSE`, optional, never asked), checked against the licenses the Gitea server offers.
|
||||
- Partial-failure reporting with a documented way to continue.
|
||||
- Starting through a command link: the script finds its own files from the link, the new project lands in the folder it was started in, and `./config.env` and `./.env` there are read before the checkout's (confirmed before the first request).
|
||||
- Starting through a command link: the script finds its own files from the link, and the new project lands in the folder it was started in.
|
||||
- Fetching the framework's own submodules (`git submodule update --init --recursive`), so the `qc` checklists are present.
|
||||
- Documentation of the SSH prerequisite for the submodule (Gitea SSH on port `10022`).
|
||||
|
||||
@@ -101,7 +101,7 @@ Supports developing on self-hosted Gitea while publishing to GitHub, and adoptin
|
||||
| 8 | Preset details | A project detail set in `config.env` is never asked; an invalid one stops the run before any request and names the key | Tests with each key set, absent, empty and invalid |
|
||||
| 9 | Credentials asked and kept | A credential missing from `.env` is asked (not echoed) instead of stopping the run; the new project's `.env` is created only after a yes, owner-only, ignored by git, holding only the keys the project needs, and an existing `.env` is never replaced without a yes | Tests: each credential present and missing, `.env` written, declined, existing, file mode, git exclusion, no token in output |
|
||||
| 10 | Project license | `PROJECT_LICENSE` set: that license is on the Gitea repository with and without GitHub; `none`: no license; absent: AGPL-3.0 only when GitHub is chosen and the project is public; a license the server does not offer stops the run before anything is created | Tests with a license set, `none`, absent and not offered |
|
||||
| 11 | Global command | Started through a command link in a `PATH` folder from another folder, the script runs, reads `./config.env` and `./.env` of that folder, else the checkout's, names them before any request, and creates the project under that folder | Test run through a link with files in the folder, in the checkout and in neither; the README example run once |
|
||||
| 11 | Global command | Started through a command link in a `PATH` folder from another folder, the script runs, reads the checkout's `config.env` and `.env` and creates the project under that folder | Test run through a link; the README example run once |
|
||||
|
||||
## Risks
|
||||
|
||||
@@ -154,4 +154,3 @@ Proceed — the procedure is small, well bounded and removes a repeated, securit
|
||||
[SA-001]: ./stakeholder-analysis.md
|
||||
[UCD-001]: ./use-case-diagram.md
|
||||
[1cd27f7]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/1cd27f77ed844773a969210a11de0d8bb98ac98f
|
||||
[0ab5006]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/0ab50068bf9e5be82a801af9dbe5b763eeaf7f31
|
||||
|
||||
+6
-17
@@ -9,8 +9,8 @@
|
||||
## Version History
|
||||
| Date | Status | Author | Reviewer | Change | Commit |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 2026-10-06 | Proposed | Jens Tirsvad Nielsen | S02 | ProjectRequest carries the license that applies (from DCD-001) | [d773fa9] |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Added Launcher, Checkout and WorkingFolder; startProjectCreation takes the checkout and working folder (from DCD-003, UC-002) | [1cd27f7] |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Default configuration files: --config and --env, else ./config.env and ./.env in the working folder, else the checkout's | [0ab5006] |
|
||||
|
||||
---
|
||||
|
||||
@@ -33,7 +33,7 @@ enum Visibility {
|
||||
}
|
||||
|
||||
class ProjectCreator <<controller>> {
|
||||
+startProjectCreation(workingFolder : WorkingFolder, configFiles : ConfigFiles) : PromptSet
|
||||
+startProjectCreation(checkout : Checkout, workingFolder : WorkingFolder, configPath : Path [0..1], envPath : Path [0..1]) : PromptSet
|
||||
+provideProjectDetails(name : String, description : String, visibility : Visibility, giteaOwner : Owner, githubOwner : Owner [0..1], directory : Path, enablePlanGate : Boolean, writeEnvFile : Boolean) : Summary
|
||||
}
|
||||
class ConfigLoader {
|
||||
@@ -83,7 +83,6 @@ class SummaryReport {
|
||||
class Launcher {
|
||||
+resolveCheckout(invocation : Path) : Checkout
|
||||
+currentFolder() : WorkingFolder
|
||||
+locateConfigFiles(configPath : Path [0..1], envPath : Path [0..1], checkout : Checkout, workingFolder : WorkingFolder) : ConfigFiles
|
||||
+startFromWorkingFolder(configPath : Path [0..1], envPath : Path [0..1]) : PromptSet
|
||||
}
|
||||
class Checkout {
|
||||
@@ -93,12 +92,6 @@ class Checkout {
|
||||
}
|
||||
class WorkingFolder {
|
||||
-path : Path
|
||||
+configFile() : Path [0..1]
|
||||
+envFile() : Path [0..1]
|
||||
}
|
||||
class ConfigFiles {
|
||||
-configFile : Path
|
||||
-envFile : Path
|
||||
}
|
||||
class Run {
|
||||
-isApply : Boolean
|
||||
@@ -193,8 +186,6 @@ class Summary {
|
||||
Launcher "1" --> "1" ProjectCreator : starts
|
||||
Launcher ..> Checkout : creates
|
||||
Launcher ..> WorkingFolder : creates
|
||||
Launcher ..> ConfigFiles : creates
|
||||
Run "1" *-- "1" ConfigFiles
|
||||
Run "1" *-- "1" Checkout
|
||||
Run "1" *-- "1" WorkingFolder
|
||||
ProjectCreator ..> ConfigLoader : creates
|
||||
@@ -258,10 +249,9 @@ Repository "0..*" --> "1" Visibility
|
||||
|
||||
| Class | Refines (Domain Model concept) | Responsibility | Attributes | Operations |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `Launcher` | Command Link (the object that follows it) | Follows the command link to the checkout, takes the folder the Maintainer stands in, chooses the two configuration files, and starts the run. | none | `resolveCheckout`, `currentFolder`, `locateConfigFiles`, `startFromWorkingFolder` |
|
||||
| `Launcher` | Command Link (the object that follows it) | Follows the command link to the checkout, takes the folder the Maintainer stands in, and starts the run. | none | `resolveCheckout`, `currentFolder`, `startFromWorkingFolder` |
|
||||
| `Checkout` | Checkout | Names the folder that holds the script's own files and the default `config.env` and `.env`. | `path` | `configFile`, `envFile` |
|
||||
| `WorkingFolder` | Working Folder | Names the base of the default directory of the new project and the files it may hold. | `path` | `configFile`, `envFile` |
|
||||
| `ConfigFiles` | none (system concept of [OC-002]) | Carries the two files chosen for the `Configuration`. | `configFile`, `envFile` | none |
|
||||
| `WorkingFolder` | Working Folder | Names the base of the default directory of the new project. | `path` | none |
|
||||
| `ProjectCreator` | none (controller for the system operations of [OC-001]) | Receives the two system operations, sequences the steps and stops on the first failure. | none | `startProjectCreation`, `provideProjectDetails` |
|
||||
| `ConfigLoader` | Configuration | Reads `config.env` and `.env` as plain text and validates every value, preset project details included. | none | `load` |
|
||||
| `CredentialCollector` | none (system concept) | Asks, without echo, for a credential that `.env` does not provide and validates it like one read from `.env`. | none | `collect` |
|
||||
@@ -301,10 +291,9 @@ Repository "0..*" --> "1" Visibility
|
||||
|
||||
| Method signature | Operation Contract / SD message |
|
||||
| --- | --- |
|
||||
| `ProjectCreator.startProjectCreation(workingFolder, configFiles) : PromptSet` | [OC-001] `startProjectCreation`; [SD-001] `startProjectCreation()`; [SD-002] `startProjectCreation(checkout, workingFolder, ...)` |
|
||||
| `ProjectCreator.startProjectCreation(checkout, workingFolder, configPath, envPath) : PromptSet` | [OC-001] `startProjectCreation`; [SD-001] `startProjectCreation()`; [SD-002] `startProjectCreation(checkout, workingFolder, ...)` |
|
||||
| `Launcher.startFromWorkingFolder(configPath, envPath) : PromptSet` | [OC-002] `startFromWorkingFolder`; [SD-002] |
|
||||
| `Launcher.resolveCheckout(invocation) : Checkout`, `Launcher.currentFolder() : WorkingFolder` | [OC-002] P2, P3; [SD-002] |
|
||||
| `Launcher.locateConfigFiles(configPath, envPath, checkout, workingFolder) : ConfigFiles`, `WorkingFolder.configFile()`, `WorkingFolder.envFile()` | [OC-002] P5; [SD-002] `locateConfigFiles(...)` |
|
||||
| `ProjectCreator.provideProjectDetails(name, description, visibility, giteaOwner, githubOwner, directory, enablePlanGate, writeEnvFile) : Summary` | [OC-001] `provideProjectDetails`; [SD-001] `provideProjectDetails(...)` |
|
||||
| `ConfigLoader.load(configFile, envFile) : Configuration` | [SD-001] `load(config.env, .env)`; [OC-001] `startProjectCreation` P2 |
|
||||
| `CredentialCollector.collect(configuration, kinds) : Configuration` | [SD-001] `collect(configuration, GITEA_TOKEN)` and `collect(configuration, GITHUB_PAT, GITHUB_USER)`; [OC-001] `startProjectCreation` P2 and the precondition of `provideProjectDetails` |
|
||||
@@ -373,5 +362,5 @@ SOLID check: no class has more than one reason to change (one host API, one kind
|
||||
[SD-001]: ./uc-001/sd.md
|
||||
[MIL-005]: ./milestones/mil-005-credentials.md
|
||||
[DICT-001]: ./dictionary.md
|
||||
[d773fa9]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/d773fa91df5a54090254e12e074880fb6526a9ff
|
||||
[1cd27f7]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/1cd27f77ed844773a969210a11de0d8bb98ac98f
|
||||
[0ab5006]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/0ab50068bf9e5be82a801af9dbe5b763eeaf7f31
|
||||
|
||||
+4
-5
@@ -9,8 +9,8 @@
|
||||
## Version History
|
||||
| Date | Status | Author | Reviewer | Change | Commit |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 2026-10-06 | Proposed | Jens Tirsvad Nielsen | S02 | LicenseFile definition no longer tied to GitHub | [d773fa9] |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Added Command Link, Checkout and Working Folder (DM-003, UC-002) | [1cd27f7] |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | `ConfigFiles` named as a system concept without a PO term | [0ab5006] |
|
||||
|
||||
---
|
||||
|
||||
@@ -49,9 +49,8 @@ Maps each Product Owner (PO) term to its professional IT term. PO language: Engl
|
||||
- The Domain Model, use cases and user stories use the PO term; the Operation
|
||||
Contract, Sequence Diagram, Design Class Diagram and ERD use the IT term.
|
||||
- One IT term per PO term and one PO term per IT term; no synonyms.
|
||||
- `Run`, `ToolCheck`, `PreflightResult`, `PromptSet` and `ConfigFiles` (the two
|
||||
files chosen for the Configuration, in [OC-002]) appear in the Operation
|
||||
Contracts but have no PO term: they are system concepts, not domain concepts, and are not
|
||||
- `Run`, `ToolCheck`, `PreflightResult` and `PromptSet` appear in [OC-001] but
|
||||
have no PO term: they are system concepts, not domain concepts, and are not
|
||||
in the Domain Model.
|
||||
- `InstallResult` and the enumeration `Visibility` appear only in [DCD-001]:
|
||||
`InstallResult` carries the three results of one operation, and `Visibility`
|
||||
@@ -68,5 +67,5 @@ Maps each Product Owner (PO) term to its professional IT term. PO language: Engl
|
||||
[DM-002]: ./domain-model.md
|
||||
[OC-001]: ./uc-001/oc.md
|
||||
[DCD-001]: ./uc-001/dcd.md
|
||||
[d773fa9]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/d773fa91df5a54090254e12e074880fb6526a9ff
|
||||
[1cd27f7]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/1cd27f77ed844773a969210a11de0d8bb98ac98f
|
||||
[0ab5006]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/0ab50068bf9e5be82a801af9dbe5b763eeaf7f31
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
## Version History
|
||||
| Date | Status | Author | Reviewer | Change | Commit |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | License: the AGPL-3.0 default needs GitHub and a public project | [1cd27f7] |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Added Command Link, Checkout and Working Folder (from DM-003, UC-002) | [1cd27f7] |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Default configuration files: --config and --env, else ./config.env and ./.env in the working folder, else the checkout's | [0ab5006] |
|
||||
|
||||
---
|
||||
|
||||
@@ -128,7 +128,6 @@ Maintainer "1" --> "0..*" CommandLink : makes
|
||||
CommandLink "0..*" --> "1" Checkout : leads to
|
||||
Maintainer "1" --> "1" WorkingFolder : starts the script in
|
||||
Checkout "1" --> "1" Configuration : holds by default
|
||||
WorkingFolder "1" --> "0..1" Configuration : may hold
|
||||
WorkingFolder "1" --> "0..*" LocalProject : is the base of
|
||||
@enduml
|
||||
```
|
||||
@@ -156,7 +155,7 @@ WorkingFolder "1" --> "0..*" LocalProject : is the base of
|
||||
| Credentials File | The file in a Local Project that holds a copy of the Access Tokens (and the GitHub account name) the project needs; readable by its owner only and ignored by git | address | [UC-001] step 9 "credentials file" |
|
||||
| Command Link | A name in a folder on the shell's search path that leads to the script in the Checkout | name, folder | [UC-002] step 1 "command link" |
|
||||
| Checkout | The folder that holds RepoFoundry: the script, its own files and by default `config.env` and `.env` | path | [UC-002] step 4 "checkout" |
|
||||
| Working Folder | The folder in which the Maintainer starts the script, under which the new project is created by default, and which may hold its own `config.env` and `.env` | path | [UC-002] step 2 "working folder" |
|
||||
| Working Folder | The folder in which the Maintainer starts the script and under which the new project is created by default | path | [UC-002] step 2 "working folder" |
|
||||
| Summary | The report of what was created, skipped or failed and how to continue | created items, skipped items, next steps | [UC-001] step 10 "summary" |
|
||||
|
||||
## Association Table
|
||||
@@ -190,7 +189,6 @@ WorkingFolder "1" --> "0..*" LocalProject : is the base of
|
||||
| Command Link | leads to | Checkout | 0..* to 1 |
|
||||
| Maintainer | starts the script in | Working Folder | 1 to 1 |
|
||||
| Checkout | holds by default | Configuration | 1 to 1 |
|
||||
| Working Folder | may hold | Configuration | 1 to 0..1 |
|
||||
| Working Folder | is the base of | Local Project | 1 to 0..* |
|
||||
|
||||
## Generalizations
|
||||
@@ -208,4 +206,3 @@ WorkingFolder "1" --> "0..*" LocalProject : is the base of
|
||||
[DICT-001]: ./dictionary.md
|
||||
[DM-001]: ./uc-001/dm.md
|
||||
[1cd27f7]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/1cd27f77ed844773a969210a11de0d8bb98ac98f
|
||||
[0ab5006]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/0ab50068bf9e5be82a801af9dbe5b763eeaf7f31
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
## Version History
|
||||
| Date | Status | Author | Reviewer | Change | Commit |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 2026-10-07 | Deprecated | Jens Tirsvad Nielsen | S02 | AGPL-3.0 default only when GitHub is chosen and the project is public (purpose, resolution order, criterion 3, tasks 1 and 4) | [1cd27f7] |
|
||||
| 2026-10-07 | Accepted | Jens Tirsvad Nielsen | S02 | Task 4 renamed so its issue title is unique (the sync matches issues by title) | [be759e3] |
|
||||
| 2026-10-06 | Proposed | Jens Tirsvad Nielsen | S02 | Initial version | [d773fa9] |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | AGPL-3.0 default only when GitHub is chosen and the project is public (purpose, resolution order, criterion 3, tasks 1 and 4) | [1cd27f7] |
|
||||
|
||||
---
|
||||
|
||||
@@ -75,7 +75,7 @@ The license that applies is resolved in this order: `PROJECT_LICENSE` when set (
|
||||
| 1 | Read and validate `PROJECT_LICENSE` | Add the key to the `config.env` parser and its validator (a license key or `none`; empty refused; errors name the key). Resolve the license that applies into the project request (key set, else AGPL-3.0 with GitHub and a public project, else none) and mark it `(from config.env)` in the summary. Never asked. Extensions of step 3 of [UC-001]. | Yes | [UC-001] |
|
||||
| 2 | Apply the license on Gitea, independent of GitHub | Generalize the preflight check from the fixed AGPL-3.0 to the license that applies (checked only when one applies); create the repository with it; the plan, the summary and the reuse warning name the license that applies instead of AGPL-3.0; GitHub receives the file through the mirror as before. Extension 4c and step 6 of [UC-001]. | Yes | [UC-001] |
|
||||
| 3 | Document the key | Commented example in `config.env.example`, a row in the README table of project details, and the rule for the license that applies, including `none` and the default. | No | |
|
||||
| 4 | Test every license case | Key set (with and without GitHub), `none`, absent (public and private, with and without GitHub), empty, invalid and not offered by the server; never asked; the summary marker; the existing tests changed only where a private project with GitHub no longer gets AGPL-3.0. | No | |
|
||||
| 4 | Test every case | Key set (with and without GitHub), `none`, absent (public and private, with and without GitHub), empty, invalid and not offered by the server; never asked; the summary marker; the existing tests changed only where a private project with GitHub no longer gets AGPL-3.0. | No | |
|
||||
|
||||
---
|
||||
|
||||
@@ -84,5 +84,5 @@ The license that applies is resolved in this order: `PROJECT_LICENSE` when set (
|
||||
[UC-001]: ../uc-001/uc.md
|
||||
[DCD-001]: ../uc-001/dcd.md
|
||||
[MIL-004]: ./mil-004-configurable-details.md
|
||||
[d773fa9]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/d773fa91df5a54090254e12e074880fb6526a9ff
|
||||
[1cd27f7]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/1cd27f77ed844773a969210a11de0d8bb98ac98f
|
||||
[be759e3]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/be759e38326eac2b11e65a2b582b2431186e338b
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
## Version History
|
||||
| Date | Status | Author | Reviewer | Change | Commit |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 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 | 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] |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Initial version | [1cd27f7] |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Added UC-002 and US-002 (global command); tasks 2 and 3 trace to UC-002 | [1cd27f7] |
|
||||
|
||||
---
|
||||
|
||||
@@ -22,7 +22,7 @@ Decide whether a new project holds the whole framework, including the `qc` check
|
||||
|
||||
1. `create-project.sh` that, after adding the `framework` submodule (and when `framework` already exists as that submodule), runs `git submodule update --init --recursive` in the new project, so `framework/qc/` holds the checklists.
|
||||
2. `create-project.sh` that finds its own files when started through a symlink, so a link in a folder on `PATH` works from any directory.
|
||||
3. A README usage section that says: run the script from the folder in which the project is to be created (the default directory is `./<name>` relative to where it is started), keep `config.env` and `.env` in the folder where the project is created or in the checkout (the folder's file wins, each file on its own), name other files with `--config` and `--env`, and make the script global with a worked example (a symlink in a `PATH` folder, with the check that it works), for Linux, macOS and Git Bash on Windows.
|
||||
3. A README usage section that says: run the script from the folder in which the project is to be created (the default directory is `./<name>` relative to where it is started), keep `config.env` and `.env` in the checkout and point to them with `--config` and `--env` or place them where the script reads them, and make the script global with a worked example (a symlink in a `PATH` folder, with the check that it works), for Linux, macOS and Git Bash on Windows.
|
||||
|
||||
## Go / No-Go Criteria
|
||||
|
||||
@@ -36,8 +36,6 @@ Decide whether a new project holds the whole framework, including the `qc` check
|
||||
| 6 | The README example for the global command was run once as written and its check passed | Verified | An example that was not run |
|
||||
| 7 | The README states the folder to start from and where the new project lands, with an example from a folder that is not the checkout | Reviewed by S02 | Missing or unclear |
|
||||
| 8 | All acceptance criteria of US-001.07 and US-002 in [US-001] are met | Verified | Any unmet |
|
||||
| 9 | With `--config` and `--env` absent, `./config.env` and `./.env` in the working folder are used, each file on its own, and the checkout's stand in for a missing one; with neither present the run stops before any request and names both places | Tests pass | A file used from another place, or a request made |
|
||||
| 10 | A file from the working folder is named with the Gitea address it holds and needs a yes, default no, before the first request; every file used is named in the output | Tests pass | A request before the yes, or a file used without being named |
|
||||
|
||||
## Dependencies
|
||||
|
||||
@@ -70,9 +68,9 @@ Decide whether a new project holds the whole framework, including the `qc` check
|
||||
| # | Task | Summary | Needs its own Use Case/User Story? | Reference |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 1 | Fetch the framework's own submodules | After `git submodule add` of the framework, and on the "already a submodule" path, run `git submodule update --init --recursive` in the new project. A failure stops the step, reports what exists and names the command to run by hand, without a credential. Step 9 and extension 9e of [UC-001]. | Yes | [UC-001] |
|
||||
| 2 | Start through a command link | Resolve `BASH_SOURCE` through links (without requiring `readlink -f`, which macOS lacks) so `SCRIPT_DIR` and `PROJECT_ROOT` point into the checkout, keep the current folder as the base of the default directory, and choose `config.env` and `.env` (named, else `./`, else the checkout's), naming them and asking a yes for a file from the working folder. Errors name the places looked in. Steps 3 to 6 and extensions 4a to 4c of [UC-002]. | Yes | [UC-002] |
|
||||
| 3 | Document the usage | Step 1 and extensions 1a and 3a of [UC-002]. README usage section: start from the folder where the project is to be created, where `config.env` and `.env` are read from (the folder first, then the checkout), the confirmation of a file from the folder, `--config` and `--env`, and the global command with a worked example and its check, for Linux, macOS and Git Bash on Windows. | Yes | [UC-002] |
|
||||
| 4 | Test the qc fetch and the command link | `qc` filled after a run and after a rerun, nested fetch failure, framework without a submodule, start through a symlink from another folder; configuration files named, in the folder, in the checkout, mixed and in neither; the confirmation answered yes and no. | No | |
|
||||
| 2 | Start through a command link | Resolve `BASH_SOURCE` through links (without requiring `readlink -f`, which macOS lacks) so `SCRIPT_DIR` and `PROJECT_ROOT` point into the checkout, and keep the current folder as the base of the default directory. Errors name the folder or path looked in. Steps 3 to 6 and extensions 4a and 4b of [UC-002]. | Yes | [UC-002] |
|
||||
| 3 | Document the usage | Step 1 and extensions 1a and 3a of [UC-002]. README usage section: start from the folder where the project is to be created, where `config.env` and `.env` are read from, `--config` and `--env`, and the global command with a worked example and its check, for Linux, macOS and Git Bash on Windows. | Yes | [UC-002] |
|
||||
| 4 | Test every case | `qc` filled after a run and after a rerun, nested fetch failure, framework without a submodule, start through a symlink from another folder. | No | |
|
||||
|
||||
---
|
||||
|
||||
@@ -81,5 +79,4 @@ Decide whether a new project holds the whole framework, including the `qc` check
|
||||
[UC-001]: ../uc-001/uc.md
|
||||
[UC-002]: ../uc-002/uc.md
|
||||
[MIL-003]: ./mil-003-scaffold-and-release.md
|
||||
[be759e3]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/be759e38326eac2b11e65a2b582b2431186e338b
|
||||
[0ab5006]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/0ab50068bf9e5be82a801af9dbe5b763eeaf7f31
|
||||
[1cd27f7]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/1cd27f77ed844773a969210a11de0d8bb98ac98f
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
# SQA Review Record: Default configuration files from the working folder
|
||||
|
||||
## Metadata
|
||||
| Key | Value |
|
||||
| --- | --- |
|
||||
| ID | RC-029 |
|
||||
| CrossReference | [MIL-007], [QC-MIL-001], [UC-002], [BC-001], [US-001], [DM-003], [DM-002], [OC-002], [SD-002], [DCD-003], [DCD-002], [DICT-001], [SSD-002] |
|
||||
|
||||
## Version History
|
||||
| Date | Status | Author | Reviewer | Change | Commit |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Initial version | [0ab5006] |
|
||||
|
||||
---
|
||||
|
||||
## Artifact Under Review
|
||||
|
||||
- Instance reviewed: the change that makes `./config.env` and `./.env` in the working folder the default configuration files (after `--config` and `--env`, before the checkout's). It touches [MIL-007] and, through it, [BC-001], [US-001], [UC-002], [SSD-002], [DM-003], [DM-002], [OC-002], [SD-002], [DCD-003], [DCD-002] and [DICT-001]. It follows [RC-022] to [RC-028], which reviewed the first version of these documents; where this record disagrees with them, this record applies.
|
||||
- Checklist used: [QC-MIL-001] for [MIL-007]; the checklists of the other types are applied to the changed parts below.
|
||||
- Review date: 2026-10-07
|
||||
|
||||
## Checklist Results ([MIL-007], QC-MIL-001)
|
||||
|
||||
| # | Criterion | Status | Evidence/Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | A concrete deliverable is defined for every gate | Pass | Deliverable 3 now names the folder-first lookup; the code and tests are named in tasks 2 and 4. |
|
||||
| 2 | Explicit Go/No-Go criteria are stated for each gate | Pass | Criteria 9 and 10 are new and objective: which file is used, that nothing is requested before the yes, and that every file used is named. |
|
||||
| 3 | Dependencies on other milestones are explicitly mapped | Pass | Unchanged: [MIL-003]. |
|
||||
| 4 | Each milestone is traceable to a Business Case objective or KPI | Pass | Objective 11 and success criterion 11 of [BC-001] now carry the lookup rule. |
|
||||
| 5 | Milestone owner and approving reviewer are identified | Pass | Unchanged: S01 and S02. |
|
||||
| 6 | Milestone has a defined target date consistent with project constraints | Pass | Unchanged: 2026-12-11. |
|
||||
|
||||
## Change checks on the other artifacts
|
||||
|
||||
| Artifact | Change | Status | Evidence/Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| [UC-002] | Precondition, step 4, extensions 4b and 4c, and two rules | Pass | Each file is chosen on its own: named, else `./`, else the checkout's. Extension 4c asks for a yes (default no) before the first request. The use case stays free of implementation detail. |
|
||||
| [SSD-002] | One sentence: the confirmation is out of scope | Pass | Same convention as the consent questions of [SSD-001]. |
|
||||
| [DM-003], [DM-002] | Association Working Folder "may hold" Configuration, 1 to 0..1; Working Folder definition | Pass | Both models changed in the same way; multiplicity on both ends. |
|
||||
| [OC-002] | `ConfigFiles` and a new P5; former P5 and P6 become P6 and P7; two exceptions | Pass | Declarative: the chosen paths are stated as values, not as a search procedure. Each exception names its failing precondition. |
|
||||
| [SD-002] | `locateConfigFiles`, creation of `ConfigFiles`, a confirmation `alt`, a new signature of `startProjectCreation` | Pass | Every postcondition has a message in the coverage table; `create` is shown for `ConfigFiles`. |
|
||||
| [DCD-003], [DCD-002] | `ConfigFiles`; `Launcher.locateConfigFiles`; `WorkingFolder.configFile` and `envFile`; the signature of `startProjectCreation` | Pass with a note | Method Traceability covers each new method. The signature of `startProjectCreation` now differs more from [DCD-001], [OC-001] and [SD-001]; this widens the action item of [RC-028]. |
|
||||
| [DICT-001] | `ConfigFiles` named as a system concept without a PO term | Pass | Treated like `Run` and `PromptSet`, as the dictionary rules allow. |
|
||||
| [BC-001], [US-001] | Objective 11, scope item and criterion 11; the acceptance criteria of US-002 | Pass | Given/when/then; the confirmation and the naming of files are testable. |
|
||||
|
||||
## Risk found in the change
|
||||
|
||||
A `config.env` in the working folder can set `GITEA_URL` to another host, and the token from `.env` would then be sent there on the first request. That is why [UC-002] extension 4c and [MIL-007] criterion 10 require the files and the Gitea address to be named and a yes before any request. The yes does not protect a Maintainer who confirms without reading, and it adds one prompt to every run that uses a folder file; if S01 finds the prompt too heavy, the alternative is to confirm only when the address in the folder's `config.env` differs from the checkout's.
|
||||
|
||||
## Overall Verdict
|
||||
|
||||
Go-with-conditions — the change is consistent across the documents, and the security risk above is answered by a criterion that can be tested. The status stays `Proposed` until the action items are closed. Drafted by Claude Code for S02; the author and reviewer are the same person for now. The verdict takes effect only when S02 confirms it.
|
||||
|
||||
## Action Items
|
||||
|
||||
| Action | Owner | Due |
|
||||
| --- | --- | --- |
|
||||
| Decide whether the confirmation is asked on every run that uses a folder file, or only when the Gitea address differs from the checkout's | S01 | Before [MIL-007] starts |
|
||||
| Settle the `startProjectCreation` signature in [OC-001], [SD-001] and [DCD-001], as in the action item of [RC-028] | S01 | Before [MIL-007] starts |
|
||||
|
||||
---
|
||||
|
||||
[MIL-007]: ../../milestones/mil-007-framework-checklists.md
|
||||
[QC-MIL-001]: ../../../framework/qc/qc-milestones-gateways.md
|
||||
[UC-002]: ../../uc-002/uc.md
|
||||
[BC-001]: ../../business-case.md
|
||||
[US-001]: ../../user-stories.md
|
||||
[DM-003]: ../../uc-002/dm.md
|
||||
[DM-002]: ../../domain-model.md
|
||||
[OC-002]: ../../uc-002/oc.md
|
||||
[SD-002]: ../../uc-002/sd.md
|
||||
[DCD-003]: ../../uc-002/dcd.md
|
||||
[DCD-002]: ../../dcd.md
|
||||
[DICT-001]: ../../dictionary.md
|
||||
[SSD-002]: ../../uc-002/ssd.md
|
||||
[SSD-001]: ../../uc-001/ssd.md
|
||||
[OC-001]: ../../uc-001/oc.md
|
||||
[SD-001]: ../../uc-001/sd.md
|
||||
[DCD-001]: ../../uc-001/dcd.md
|
||||
[RC-022]: ./rc-022-mil-007.md
|
||||
[RC-028]: ./rc-028-dcd-003.md
|
||||
[MIL-003]: ../../milestones/mil-003-scaffold-and-release.md
|
||||
[0ab5006]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/0ab50068bf9e5be82a801af9dbe5b763eeaf7f31
|
||||
@@ -24,7 +24,7 @@ updated whenever an artifact instance is created or reviewed.
|
||||
|
||||
| Artifact Instance | Type | Upstream (Backward Link) | Downstream (Forward Link) | Last Reviewed (RC-ID) |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| [BC-001] | BC | - | [SA-001], [PP-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [MIL-007], [US-001], [UCD-001] | [RC-010], [RC-018], [RC-020], [RC-022], [RC-029] |
|
||||
| [BC-001] | BC | - | [SA-001], [PP-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [MIL-007], [US-001], [UCD-001] | [RC-010], [RC-018], [RC-020], [RC-022] |
|
||||
| [SA-001] | SA | [BC-001] | [UCD-001], [UC-001], [DICT-001] | [RC-013] |
|
||||
| [PP-001] | PP | [BC-001], [SA-001] | [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [MIL-007] | [RC-012], [RC-018], [RC-020], [RC-022] |
|
||||
| [MIL-001] | MIL | [BC-001], [PP-001] | [US-001] | [RC-011], [RC-016] |
|
||||
@@ -33,24 +33,24 @@ updated whenever an artifact instance is created or reviewed.
|
||||
| [MIL-004] | MIL | [BC-001], [PP-001] | [US-001] | [RC-018], [RC-019] |
|
||||
| [MIL-005] | MIL | [BC-001], [PP-001] | [US-001] | [RC-020] |
|
||||
| [MIL-006] | MIL | [BC-001], [PP-001] | [US-001] | [RC-022] |
|
||||
| [MIL-007] | MIL | [BC-001], [PP-001] | [US-001], [UC-002] | [RC-022], [RC-029] |
|
||||
| [MIL-007] | MIL | [BC-001], [PP-001] | [US-001], [UC-002] | [RC-022] |
|
||||
| [UCD-001] | UCD | [BC-001], [SA-001] | [US-001], [UC-001], [UC-002] | [RC-009], [RC-023] |
|
||||
| [US-001] | US | [BC-001], [UCD-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [MIL-007] | [UC-001] | [RC-001], [RC-020], [RC-022], [RC-029] |
|
||||
| [US-001] | US | [BC-001], [UCD-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [MIL-007] | [UC-001] | [RC-001], [RC-020], [RC-022] |
|
||||
| [UC-001] | UC | [UCD-001], [US-001], [SA-001] | [SSD-001], [DM-001] | [RC-002], [RC-020], [RC-023] |
|
||||
| [UC-002] | UC | [UCD-001], [US-001], [SA-001], [BC-001] | [SSD-002], [DM-003] | [RC-023], [RC-029] |
|
||||
| [SSD-002] | SSD | [UC-002], [DM-003] | [OC-002] | [RC-024], [RC-029] |
|
||||
| [DM-003] | DM | [UC-002], [UCD-001], [SSD-002], [DICT-001], [DM-001] | [OC-002], [DCD-003] | [RC-025], [RC-029] |
|
||||
| [OC-002] | OC | [SSD-002], [DM-003] | [SD-002] | [RC-026], [RC-029] |
|
||||
| [SD-002] | SD | [OC-002], [DCD-003] | [DCD-003] | [RC-027], [RC-029] |
|
||||
| [DCD-003] | DCD | [DM-003], [SD-002], [DICT-001], [UC-002], [DCD-001] | [DCD-002] | [RC-028], [RC-029] |
|
||||
| [UC-002] | UC | [UCD-001], [US-001], [SA-001], [BC-001] | [SSD-002], [DM-003] | [RC-023] |
|
||||
| [SSD-002] | SSD | [UC-002], [DM-003] | [OC-002] | [RC-024] |
|
||||
| [DM-003] | DM | [UC-002], [UCD-001], [SSD-002], [DICT-001], [DM-001] | [OC-002], [DCD-003] | [RC-025] |
|
||||
| [OC-002] | OC | [SSD-002], [DM-003] | [SD-002] | [RC-026] |
|
||||
| [SD-002] | SD | [OC-002], [DCD-003] | [DCD-003] | [RC-027] |
|
||||
| [DCD-003] | DCD | [DM-003], [SD-002], [DICT-001], [UC-002], [DCD-001] | [DCD-002] | [RC-028] |
|
||||
| [SSD-001] | SSD | [UC-001] | [OC-001] | [RC-003], [RC-020] |
|
||||
| [DM-001] | DM | [UC-001], [SSD-001] | [DM-002], [DICT-001], [OC-001], [DCD-001] | [RC-004], [RC-020] |
|
||||
| [DM-002] | DM | [DM-001] | [DICT-001], [DCD-001], [DCD-002] | [RC-005], [RC-020], [RC-025], [RC-029] |
|
||||
| [DICT-001] | DICT | [BC-001], [SA-001], [DM-001], [DM-002] | [OC-001], [SD-001] | [RC-008], [RC-020], [RC-025], [RC-029] |
|
||||
| [DM-002] | DM | [DM-001] | [DICT-001], [DCD-001], [DCD-002] | [RC-005], [RC-020], [RC-025] |
|
||||
| [DICT-001] | DICT | [BC-001], [SA-001], [DM-001], [DM-002] | [OC-001], [SD-001] | [RC-008], [RC-020], [RC-025] |
|
||||
| [OC-001] | OC | [SSD-001], [DM-001] | [SD-001] | [RC-006], [RC-020], [RC-026] |
|
||||
| [SD-001] | SD | [OC-001] | [DCD-001] | [RC-007], [RC-020], [RC-021] |
|
||||
| [DCD-001] | DCD | [UC-001], [DM-001], [DM-002], [OC-001], [SD-001], [DICT-001] | [DCD-002] | [RC-021] |
|
||||
| [DCD-002] | DCD | [DCD-001], [DCD-003], [DM-002], [DICT-001] | - | [RC-021], [RC-028], [RC-029] |
|
||||
| [DCD-002] | DCD | [DCD-001], [DCD-003], [DM-002], [DICT-001] | - | [RC-021], [RC-028] |
|
||||
|
||||
## Coverage Notes
|
||||
|
||||
@@ -80,7 +80,6 @@ updated whenever an artifact instance is created or reviewed.
|
||||
[RC-026]: ./reviews/rc-026-oc-002.md
|
||||
[RC-027]: ./reviews/rc-027-sd-002.md
|
||||
[RC-028]: ./reviews/rc-028-dcd-003.md
|
||||
[RC-029]: ./reviews/rc-029-default-config-files.md
|
||||
[DCD-001]: ../uc-001/dcd.md
|
||||
[DCD-002]: ../dcd.md
|
||||
[UCD-001]: ../use-case-diagram.md
|
||||
|
||||
+6
-20
@@ -10,7 +10,6 @@
|
||||
| Date | Status | Author | Reviewer | Change | Commit |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Initial version | [1cd27f7] |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Default configuration files: --config and --env, else ./config.env and ./.env in the working folder, else the checkout's | [0ab5006] |
|
||||
|
||||
---
|
||||
|
||||
@@ -25,7 +24,6 @@ Covers [UC-002]. Adds the classes that start the script through a command link.
|
||||
class Launcher {
|
||||
+resolveCheckout(invocation : Path) : Checkout
|
||||
+currentFolder() : WorkingFolder
|
||||
+locateConfigFiles(configPath : Path [0..1], envPath : Path [0..1], checkout : Checkout, workingFolder : WorkingFolder) : ConfigFiles
|
||||
+startFromWorkingFolder(configPath : Path [0..1], envPath : Path [0..1]) : PromptSet
|
||||
}
|
||||
class Checkout {
|
||||
@@ -35,22 +33,14 @@ class Checkout {
|
||||
}
|
||||
class WorkingFolder {
|
||||
-path : Path
|
||||
+configFile() : Path [0..1]
|
||||
+envFile() : Path [0..1]
|
||||
}
|
||||
class ConfigFiles {
|
||||
-configFile : Path
|
||||
-envFile : Path
|
||||
}
|
||||
class ProjectCreator {
|
||||
+startProjectCreation(workingFolder : WorkingFolder, configFiles : ConfigFiles) : PromptSet
|
||||
+startProjectCreation(checkout : Checkout, workingFolder : WorkingFolder, configPath : Path [0..1], envPath : Path [0..1]) : PromptSet
|
||||
}
|
||||
class Run
|
||||
Launcher "1" --> "1" ProjectCreator : starts
|
||||
Launcher ..> Checkout : creates
|
||||
Launcher ..> WorkingFolder : creates
|
||||
Launcher ..> ConfigFiles : creates
|
||||
Run "1" *-- "1" ConfigFiles
|
||||
Run "1" *-- "1" Checkout
|
||||
Run "1" *-- "1" WorkingFolder
|
||||
@enduml
|
||||
@@ -60,10 +50,9 @@ Run "1" *-- "1" WorkingFolder
|
||||
|
||||
| Class | Refines (Domain Model concept) | Responsibility | Attributes | Operations |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `Launcher` | Command Link (the object that follows it) | Follows the command link to the checkout, takes the folder the Maintainer stands in, chooses the two configuration files, and starts the run. | none | `resolveCheckout`, `currentFolder`, `locateConfigFiles`, `startFromWorkingFolder` |
|
||||
| `Launcher` | Command Link (the object that follows it) | Follows the command link to the checkout, takes the folder the Maintainer stands in, and starts the run. | none | `resolveCheckout`, `currentFolder`, `startFromWorkingFolder` |
|
||||
| `Checkout` | Checkout | Names the folder that holds the script's own files and the default `config.env` and `.env`. | `path` | `configFile`, `envFile` |
|
||||
| `WorkingFolder` | Working Folder | Names the base of the default directory of the new project and the files it may hold. | `path` | `configFile`, `envFile` |
|
||||
| `ConfigFiles` | none (system concept of [OC-002]) | Carries the two files chosen for the `Configuration`. | `configFile`, `envFile` | none |
|
||||
| `WorkingFolder` | Working Folder | Names the base of the default directory of the new project. | `path` | none |
|
||||
|
||||
The concept Command Link has no class: it is a link the Maintainer makes with the shell, and the system only follows it.
|
||||
|
||||
@@ -74,10 +63,8 @@ The concept Command Link has no class: it is a link the Maintainer makes with th
|
||||
| `Launcher.startFromWorkingFolder(configPath, envPath) : PromptSet` | [SD-002] `startFromWorkingFolder`; P1, P6 |
|
||||
| `Launcher.resolveCheckout(invocation) : Checkout` | [SD-002] `resolveCheckout(invocation)`; P2 |
|
||||
| `Launcher.currentFolder() : WorkingFolder` | [SD-002] `currentFolder()`; P3 |
|
||||
| `Launcher.locateConfigFiles(configPath, envPath, checkout, workingFolder) : ConfigFiles` | [SD-002] `locateConfigFiles(...)`; P5 |
|
||||
| `WorkingFolder.configFile() : Path [0..1]`, `WorkingFolder.envFile() : Path [0..1]` | [SD-002] `locateConfigFiles`; P5 |
|
||||
| `Checkout.configFile() : Path`, `Checkout.envFile() : Path` | [SD-002] `locateConfigFiles`; P5 |
|
||||
| `ProjectCreator.startProjectCreation(workingFolder, configFiles) : PromptSet` | [SD-002] `startProjectCreation`; P4, P6, P7. Replaces the signature of [DCD-001] by adding `workingFolder` and `configFiles` |
|
||||
| `Checkout.configFile() : Path`, `Checkout.envFile() : Path` | [SD-002] `startProjectCreation`; P5 |
|
||||
| `ProjectCreator.startProjectCreation(checkout, workingFolder, configPath, envPath) : PromptSet` | [SD-002] `startProjectCreation`; P4, P5, P6. Replaces the signature of [DCD-001] by adding `checkout`, `workingFolder`, `configPath` and `envPath` |
|
||||
|
||||
## Pattern Annotations
|
||||
|
||||
@@ -88,7 +75,7 @@ The concept Command Link has no class: it is a link the Maintainer makes with th
|
||||
|
||||
## Dependency Check
|
||||
|
||||
`Launcher` depends on `ProjectCreator`, `Checkout`, `WorkingFolder` and `ConfigFiles`; none of them depends on `Launcher`, so no cycle is added.
|
||||
`Launcher` depends on `ProjectCreator`, `Checkout` and `WorkingFolder`; none of them depends on `Launcher`, so no cycle is added.
|
||||
|
||||
---
|
||||
|
||||
@@ -99,4 +86,3 @@ The concept Command Link has no class: it is a link the Maintainer makes with th
|
||||
[DCD-001]: ../uc-001/dcd.md
|
||||
[DCD-002]: ../dcd.md
|
||||
[1cd27f7]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/1cd27f77ed844773a969210a11de0d8bb98ac98f
|
||||
[0ab5006]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/0ab50068bf9e5be82a801af9dbe5b763eeaf7f31
|
||||
|
||||
+1
-5
@@ -10,7 +10,6 @@
|
||||
| Date | Status | Author | Reviewer | Change | Commit |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Initial version | [1cd27f7] |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Default configuration files: --config and --env, else ./config.env and ./.env in the working folder, else the checkout's | [0ab5006] |
|
||||
|
||||
---
|
||||
|
||||
@@ -47,7 +46,6 @@ Maintainer "1" --> "0..*" CommandLink : makes
|
||||
CommandLink "0..*" --> "1" Checkout : leads to
|
||||
Maintainer "1" --> "1" WorkingFolder : starts the script in
|
||||
Checkout "1" --> "1" Configuration : holds by default
|
||||
WorkingFolder "1" --> "0..1" Configuration : may hold
|
||||
WorkingFolder "1" --> "0..*" LocalProject : is the base of
|
||||
@enduml
|
||||
```
|
||||
@@ -58,7 +56,7 @@ WorkingFolder "1" --> "0..*" LocalProject : is the base of
|
||||
| --- | --- | --- | --- |
|
||||
| Command Link | A name in a folder on the shell's search path that leads to the script in the checkout | name, folder | [UC-002] step 1 "command link" |
|
||||
| Checkout | The folder that holds RepoFoundry: the script, its own files and by default `config.env` and `.env` | path | [UC-002] step 4 "checkout" |
|
||||
| Working Folder | The folder in which the Maintainer starts the script, under which the new project is created by default, and which may hold its own `config.env` and `.env` | path | [UC-002] step 2 "working folder" |
|
||||
| Working Folder | The folder in which the Maintainer starts the script and under which the new project is created by default | path | [UC-002] step 2 "working folder" |
|
||||
|
||||
## Association Table
|
||||
|
||||
@@ -68,7 +66,6 @@ WorkingFolder "1" --> "0..*" LocalProject : is the base of
|
||||
| Command Link | leads to | Checkout | 0..* to 1 |
|
||||
| Maintainer | starts the script in | Working Folder | 1 to 1 |
|
||||
| Checkout | holds by default | Configuration | 1 to 1 |
|
||||
| Working Folder | may hold | Configuration | 1 to 0..1 |
|
||||
| Working Folder | is the base of | Local Project | 1 to 0..* |
|
||||
|
||||
## Generalizations
|
||||
@@ -86,4 +83,3 @@ WorkingFolder "1" --> "0..*" LocalProject : is the base of
|
||||
[DM-001]: ../uc-001/dm.md
|
||||
[DM-002]: ../domain-model.md
|
||||
[1cd27f7]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/1cd27f77ed844773a969210a11de0d8bb98ac98f
|
||||
[0ab5006]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/0ab50068bf9e5be82a801af9dbe5b763eeaf7f31
|
||||
|
||||
+6
-10
@@ -10,11 +10,10 @@
|
||||
| Date | Status | Author | Reviewer | Change | Commit |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Initial version | [1cd27f7] |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Default configuration files: --config and --env, else ./config.env and ./.env in the working folder, else the checkout's | [0ab5006] |
|
||||
|
||||
---
|
||||
|
||||
Concepts below use the IT terms of [DICT-001] for the PO concepts of [DM-003]. `Run` and `PromptSet` are the system concepts of [OC-001]; `ConfigFiles` is a system concept of this contract: the two files chosen for the `Configuration`. The operation `provideProjectDetails` is the one of [OC-001]; the only change is the base of its default `directory`, stated in P4.
|
||||
Concepts below use the IT terms of [DICT-001] for the PO concepts of [DM-003]. `Run` and `PromptSet` are the system concepts of [OC-001]. The operation `provideProjectDetails` is the one of [OC-001]; the only change is the base of its default `directory`, stated in P4.
|
||||
|
||||
## Contract: startFromWorkingFolder
|
||||
|
||||
@@ -22,7 +21,7 @@ Concepts below use the IT terms of [DICT-001] for the PO concepts of [DM-003]. `
|
||||
| --- | --- |
|
||||
| Operation | `startFromWorkingFolder(configPath: Path [0..1], envPath: Path [0..1]): PromptSet` |
|
||||
| Traces to | `startFromWorkingFolder` in [SSD-002] |
|
||||
| Concepts | Run, CommandLink, Checkout, WorkingFolder, ConfigFiles, Configuration |
|
||||
| Concepts | Run, CommandLink, Checkout, WorkingFolder, Configuration |
|
||||
|
||||
**Preconditions**
|
||||
|
||||
@@ -35,18 +34,16 @@ Concepts below use the IT terms of [DICT-001] for the PO concepts of [DM-003]. `
|
||||
- P2. A `Checkout` instance was created and associated with the `Run`, with `path` set to the folder that holds the script's own files, reached through the `CommandLink`, however many links lie between them.
|
||||
- P3. A `WorkingFolder` instance was created and associated with the `Run`, with `path` set to the folder in which the Maintainer started the script. It was not changed by following the `CommandLink`.
|
||||
- P4. The default of `directory` in the `PromptSet` is `./<name>` under the `WorkingFolder`, never under the `Checkout`.
|
||||
- P5. A `ConfigFiles` instance was created and associated with the `Run`. Its `configFile` is `configPath` when given, otherwise `config.env` in the `WorkingFolder` when it exists, otherwise `config.env` in the `Checkout`; its `envFile` is chosen in the same way from `envPath` and `.env`. Each file is chosen on its own. The paths are named in the output before any request to a host.
|
||||
- P6. A `Configuration` instance was created and associated with the `Run` from the `ConfigFiles`; the validation of [OC-001] `startProjectCreation` P2 and P3 applies.
|
||||
- P7. The `Run` was associated with a `PromptSet` that is returned.
|
||||
- P5. A `Configuration` instance was created and associated with the `Run` from `configPath`, or from `config.env` in the `Checkout` when `configPath` is absent, and from `envPath`, or from `.env` in the `Checkout` when `envPath` is absent; the validation of [OC-001] `startProjectCreation` P2 and P3 applies.
|
||||
- P6. The `Run` was associated with a `PromptSet` that is returned.
|
||||
|
||||
**Exceptions**
|
||||
|
||||
| Condition (failing precondition) | Outcome |
|
||||
| --- | --- |
|
||||
| The `Checkout`'s own files are not found from the link target | The `Run` ends with an error naming the folder it looked in; nothing was changed |
|
||||
| `config.env` or `.env` is not named and is in neither the `WorkingFolder` nor the `Checkout` (P5) | The `Run` ends with an error naming both places it looked in and the options `--config` and `--env`; nothing was changed |
|
||||
| A chosen file is in the `WorkingFolder` and the Maintainer does not confirm it (P5) | The `Run` ends before any request to a host; nothing was changed |
|
||||
| A value in `config.env` or `.env` is malformed (P6) | As in [OC-001] `startProjectCreation`: the error names the key, never its value; nothing was changed |
|
||||
| `config.env` or `.env` is not found in the `Checkout` and no path was given (P5) | The `Run` ends with an error naming the path it looked in and the options `--config` and `--env`; nothing was changed |
|
||||
| A value in `config.env` or `.env` is malformed (P5) | As in [OC-001] `startProjectCreation`: the error names the key, never its value; nothing was changed |
|
||||
|
||||
---
|
||||
|
||||
@@ -55,4 +52,3 @@ Concepts below use the IT terms of [DICT-001] for the PO concepts of [DM-003]. `
|
||||
[DICT-001]: ../dictionary.md
|
||||
[OC-001]: ../uc-001/oc.md
|
||||
[1cd27f7]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/1cd27f77ed844773a969210a11de0d8bb98ac98f
|
||||
[0ab5006]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/0ab50068bf9e5be82a801af9dbe5b763eeaf7f31
|
||||
|
||||
+11
-22
@@ -10,7 +10,6 @@
|
||||
| Date | Status | Author | Reviewer | Change | Commit |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Initial version | [1cd27f7] |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Default configuration files: --config and --env, else ./config.env and ./.env in the working folder, else the checkout's | [0ab5006] |
|
||||
|
||||
---
|
||||
|
||||
@@ -29,7 +28,6 @@ participant ":Launcher" as L
|
||||
participant ":ProjectCreator" as PC
|
||||
participant ":Checkout" as CK
|
||||
participant ":WorkingFolder" as WF
|
||||
participant ":ConfigFiles" as CF
|
||||
|
||||
Maintainer -> L : startFromWorkingFolder(configPath, envPath)
|
||||
activate L
|
||||
@@ -39,20 +37,13 @@ L -> CK : Checkout(path)
|
||||
L -> L : currentFolder()
|
||||
create WF
|
||||
L -> WF : WorkingFolder(path)
|
||||
L -> L : locateConfigFiles(configPath, envPath, checkout, workingFolder)
|
||||
create CF
|
||||
L -> CF : ConfigFiles(configFile, envFile)
|
||||
alt a chosen file is in the working folder
|
||||
L -> Maintainer : confirm the files and the Gitea address
|
||||
Maintainer --> L : yes or no
|
||||
end
|
||||
alt the Checkout's own files, or both places for a config file, are not found
|
||||
alt the Checkout's own files are not found
|
||||
L --> Maintainer : error naming the folder looked in
|
||||
end
|
||||
L -> PC : startProjectCreation(workingFolder, configFiles)
|
||||
L -> PC : startProjectCreation(checkout, workingFolder, configPath, envPath)
|
||||
activate PC
|
||||
alt a value in config.env or .env is malformed
|
||||
PC --> L : error naming the key
|
||||
alt config.env or .env not found, or a value malformed
|
||||
PC --> L : error naming the path or the key
|
||||
end
|
||||
PC --> L : promptSet
|
||||
deactivate PC
|
||||
@@ -65,9 +56,9 @@ deactivate L
|
||||
|
||||
| Pattern (GRASP / GoF) | Applied to | Rationale |
|
||||
| --- | --- | --- |
|
||||
| Information Expert | `Launcher.resolveCheckout`, `Launcher.locateConfigFiles` | The launcher knows how the script was invoked, so it is the one that can follow the command link |
|
||||
| Information Expert | `Launcher.resolveCheckout` | The launcher knows how the script was invoked, so it is the one that can follow the command link |
|
||||
| Controller | `Launcher` | One object takes the system operation and hands the work to `ProjectCreator`; `ProjectCreator` stays unaware of links |
|
||||
| Low Coupling | `ProjectCreator` receives `workingFolder` and `configFiles` as values | The use case [UC-001] runs unchanged whatever way the script was started |
|
||||
| Low Coupling | `ProjectCreator` receives `checkout` and `workingFolder` as values | The use case [UC-001] runs unchanged whatever way the script was started |
|
||||
|
||||
### Postcondition Coverage
|
||||
|
||||
@@ -76,15 +67,14 @@ deactivate L
|
||||
| P1 Run | `startFromWorkingFolder` (the run starts with it) |
|
||||
| P2 Checkout | `resolveCheckout(invocation)` and the creation of `Checkout` |
|
||||
| P3 WorkingFolder | `currentFolder()` and the creation of `WorkingFolder` |
|
||||
| P4 default directory under the WorkingFolder | `startProjectCreation(workingFolder, configFiles)`; the prompt default is built from `workingFolder` |
|
||||
| P5 ConfigFiles | `locateConfigFiles(...)` and the creation of `ConfigFiles`; the paths are named before any request |
|
||||
| P6 Configuration | `startProjectCreation(workingFolder, configFiles)` loads the two files (`load` of [SD-001]) |
|
||||
| P7 PromptSet | the returned `promptSet` |
|
||||
| Exceptions: files not found, not confirmed, or malformed | the `alt` fragments |
|
||||
| P4 default directory under the WorkingFolder | `startProjectCreation(checkout, workingFolder, ...)`; the prompt default is built from `workingFolder` |
|
||||
| P5 Configuration | `startProjectCreation` loads `config.env` and `.env` from `checkout` or from the given paths (`load` of [SD-001]) |
|
||||
| P6 PromptSet | the returned `promptSet` |
|
||||
| Exceptions: files not found; configuration not found or malformed | the two `alt` fragments |
|
||||
|
||||
### Responsibility Check
|
||||
|
||||
`Launcher` only finds the checkout, the working folder and the two files; it reads no configuration and makes no repository. `ProjectCreator` keeps every other responsibility of [SD-001].
|
||||
`Launcher` only finds the checkout and the working folder; it reads no configuration and makes no repository. `ProjectCreator` keeps every other responsibility of [SD-001].
|
||||
|
||||
---
|
||||
|
||||
@@ -93,4 +83,3 @@ deactivate L
|
||||
[DICT-001]: ../dictionary.md
|
||||
[SD-001]: ../uc-001/sd.md
|
||||
[1cd27f7]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/1cd27f77ed844773a969210a11de0d8bb98ac98f
|
||||
[0ab5006]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/0ab50068bf9e5be82a801af9dbe5b763eeaf7f31
|
||||
|
||||
+1
-4
@@ -10,7 +10,6 @@
|
||||
| Date | Status | Author | Reviewer | Change | Commit |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Initial version | [1cd27f7] |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Names the confirmation of a configuration file from the working folder as out of scope | [0ab5006] |
|
||||
|
||||
---
|
||||
|
||||
@@ -38,7 +37,7 @@ S --> A : creation summary with the project's full path
|
||||
| 3 to 4 | startFromWorkingFolder | configPath (optional), envPath (optional) | prompts for project details whose default directory is under the working folder | 3, 4, 5 |
|
||||
| 5 | provideProjectDetails | the parameters of `provideProjectDetails` in [UC-001] | creation summary that names the full path of the new project | 5, 6 |
|
||||
|
||||
Failure flows (extensions 1a, 3a, 4a and 4b of [UC-002]) are out of scope for this diagram: they end the run with a message and add no system operation. The confirmation of a file from the working folder (extension 4c) is a prompt from the system, out of scope as the consent questions of [SSD-001] are. `provideProjectDetails` is the operation of [UC-001] and is not repeated in [OC-002]; the only difference is the base of the default `directory`. Making the command link (step 1) and opening the shell (step 2) are done by the Maintainer outside the system, so they are not system operations.
|
||||
Failure flows (extensions 1a, 3a, 4a and 4b of [UC-002]) are out of scope for this diagram: they end the run with a message and add no system operation. `provideProjectDetails` is the operation of [UC-001] and is not repeated in [OC-002]; the only difference is the base of the default `directory`. Making the command link (step 1) and opening the shell (step 2) are done by the Maintainer outside the system, so they are not system operations.
|
||||
|
||||
## Lifecycle Notes
|
||||
|
||||
@@ -49,6 +48,4 @@ One script run, as in [UC-001]. The command link persists between runs; the syst
|
||||
[UC-002]: ./uc.md
|
||||
[DM-003]: ./dm.md
|
||||
[OC-002]: ./oc.md
|
||||
[SSD-001]: ../uc-001/ssd.md
|
||||
[1cd27f7]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/1cd27f77ed844773a969210a11de0d8bb98ac98f
|
||||
[0ab5006]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/0ab50068bf9e5be82a801af9dbe5b763eeaf7f31
|
||||
|
||||
+5
-10
@@ -10,7 +10,6 @@
|
||||
| Date | Status | Author | Reviewer | Change | Commit |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Initial version | [1cd27f7] |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Default configuration files: --config and --env, else ./config.env and ./.env in the working folder, else the checkout's; the files used are named and a file from the working folder is confirmed (step 4, extensions 4b and 4c, rules) | [0ab5006] |
|
||||
|
||||
---
|
||||
|
||||
@@ -26,7 +25,7 @@
|
||||
- S02 — starting through a link never reads or writes outside the checkout and the current folder
|
||||
- S03 — the README says exactly how to make the command global
|
||||
- **Preconditions:**
|
||||
- The checkout of RepoFoundry exists. `config.env` and `.env` exist as described in the README in the working folder or in the checkout, or the Maintainer points to them with `--config` and `--env`.
|
||||
- The checkout of RepoFoundry exists and holds `config.env` and `.env` as described in the README, or the Maintainer points to them with `--config` and `--env`.
|
||||
- A folder that is on the shell's `PATH` exists and the Maintainer may write to it.
|
||||
- **Postconditions (success guarantee):**
|
||||
- A command link exists in a `PATH` folder and leads to the script in the checkout.
|
||||
@@ -38,7 +37,7 @@
|
||||
1. The Maintainer makes the script reachable by name: creates a command link in a `PATH` folder that leads to `src/create-project.sh` in the checkout (the README gives the command).
|
||||
2. The Maintainer opens a shell in the folder in which the new project is to be created (the working folder).
|
||||
3. The Maintainer starts the script by the name of the command link.
|
||||
4. The system follows the command link to the checkout, loads its own files from there, and chooses `config.env` and `.env`, each one separately: the file named by `--config` or `--env`, otherwise the one in the working folder, otherwise the one in the checkout. It names the files it will use before any request to a host.
|
||||
4. The system follows the command link to the checkout, loads its own files from there, and reads `config.env` and `.env` from the checkout, or from the files named by `--config` and `--env`.
|
||||
5. The system runs [UC-001] (`<<include>>`) with the working folder as the base of the default directory of the new project.
|
||||
6. The system reports a summary that names the full path of the new project.
|
||||
|
||||
@@ -50,10 +49,8 @@
|
||||
1. The shell reports that the command cannot run; the README says how to recreate the link.
|
||||
- 4a. The checkout's own files cannot be found from the link target:
|
||||
1. The system stops before any change and names the folder it looked in.
|
||||
- 4b. `config.env` or `.env` is not named, and is in neither the working folder nor the checkout:
|
||||
1. The system stops before any change, names both places it looked in and says that `--config` and `--env` can point elsewhere.
|
||||
- 4c. A chosen file comes from the working folder:
|
||||
1. The system names the file and the Gitea address it holds and asks the Maintainer to confirm, default no, before any request to a host; on no, the system stops before any request and any change.
|
||||
- 4b. `config.env` or `.env` is not found:
|
||||
1. The system stops before any change, names the path it looked in and says that `--config` and `--env` can point elsewhere.
|
||||
|
||||
### Special Requirements / Business Rules
|
||||
|
||||
@@ -61,8 +58,7 @@
|
||||
| --- | --- |
|
||||
| 1 | The command link is made by the Maintainer with the shell, not by the script; the script never edits `PATH`, a shell profile or a system folder |
|
||||
| 4 | The system finds its own files by following the command link, however many links lie on the way, on every supported platform |
|
||||
| 4 | `config.env` and `.env` are chosen one by one in this order: `--config` / `--env`; `./config.env` / `./.env` in the working folder; the checkout's. A project may therefore use its own `.env` with the checkout's `config.env` |
|
||||
| 4 | A folder can hold a `config.env` that points the Gitea address elsewhere, and so send the token there. A file from the working folder is therefore confirmed before the first request, and every file used is named in the output |
|
||||
| 4 | `config.env` and `.env` are read from the checkout, never from the working folder, unless `--config` and `--env` name them |
|
||||
| 5 | The base of the default directory is the working folder, never the checkout |
|
||||
| 3 to 6 | Behaviour, prompts and summary are the same as when the script is started by its path from the checkout |
|
||||
|
||||
@@ -77,4 +73,3 @@
|
||||
[SA-001]: ../stakeholder-analysis.md
|
||||
[BC-001]: ../business-case.md
|
||||
[1cd27f7]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/1cd27f77ed844773a969210a11de0d8bb98ac98f
|
||||
[0ab5006]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/0ab50068bf9e5be82a801af9dbe5b763eeaf7f31
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
## Version History
|
||||
| Date | Status | Author | Reviewer | Change | Commit |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | US-001.06: AGPL-3.0 default only for a public project with GitHub; US-001.07: the framework's own submodules (qc) are fetched | [1cd27f7] |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Added US-002 (global command from the target folder) for UC-002 | [1cd27f7] |
|
||||
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | US-002: the default configuration files are the working folder's, then the checkout's; confirmed and named | [0ab5006] |
|
||||
|
||||
---
|
||||
|
||||
@@ -136,11 +136,10 @@ One further epic, "Start the script as a global command" ([UC-002]), with one st
|
||||
|
||||
**Acceptance Criteria**
|
||||
|
||||
- Given a command link in a folder on `PATH` that leads to the script, when the Maintainer starts it by name from another folder, then the script runs and finds its own files.
|
||||
- Given a command link in a folder on `PATH` that leads to the script, when the Maintainer starts it by name from another folder, then the script runs, finds its own files and reads `config.env` and `.env` from the checkout.
|
||||
- Given the Maintainer stands in a folder, when the project directory is not preset, then its default is `./<name>` under that folder, never under the checkout.
|
||||
- Given `--config` and `--env` name files, then those are read. Given they are not named, then `./config.env` and `./.env` in the folder the Maintainer stands in are read, each one that exists, and the checkout's file stands in for one that does not.
|
||||
- Given a file comes from the folder the Maintainer stands in, then the script names it and the Gitea address it holds and asks for a yes, default no, before any request; every file used is named in the output.
|
||||
- Given a file is named nowhere, in neither folder, or a link is broken, then the script stops before any change and names both places it looked in.
|
||||
- Given `--config` and `--env` name other files, then those are read instead of the checkout's.
|
||||
- Given a missing file or a broken link, then the script stops before any change and names the folder or path it looked in.
|
||||
- Given the README, then it shows the command that makes the link, the check that it works and a run from a folder that is not the checkout.
|
||||
|
||||
| Traces to | Size | INVEST exceptions |
|
||||
@@ -166,4 +165,3 @@ Valuable, Negotiable, Estimable, Small and Testable hold for each story. Indepen
|
||||
[MIL-007]: ./milestones/mil-007-framework-checklists.md
|
||||
[PP-001]: ./project-plan.md
|
||||
[1cd27f7]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/1cd27f77ed844773a969210a11de0d8bb98ac98f
|
||||
[0ab5006]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/0ab50068bf9e5be82a801af9dbe5b763eeaf7f31
|
||||
|
||||
+25
-39
@@ -10,9 +10,10 @@
|
||||
# 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. The license of the Gitea repository is
|
||||
# PROJECT_LICENSE in config.env (none means no license); without it AGPL-3.0
|
||||
# applies only when GitHub is chosen and the project is public. No commit is made in the new project.
|
||||
# chosen) and its templates. Choosing GitHub also applies the AGPL-3.0
|
||||
# license to the Gitea repository. After a yes (default no) it also writes
|
||||
# the new project's own .env with the credentials the project needs. No
|
||||
# commit is made in the new project.
|
||||
#
|
||||
# Dry run by default
|
||||
# Without --apply the script only reads from GitHub and Gitea (GET
|
||||
@@ -28,7 +29,8 @@
|
||||
# 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)
|
||||
# --env FILE credentials (default: .env in the project root); optional:
|
||||
# a credential it does not provide is asked, not echoed
|
||||
# -h, --help show this help
|
||||
# --version show the version
|
||||
#
|
||||
@@ -37,7 +39,8 @@
|
||||
# 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
|
||||
# .env GITHUB_PAT, GITHUB_USER, GITEA_TOKEN (all optional, each
|
||||
# asked when missing)
|
||||
#
|
||||
# Environment
|
||||
# REPOFOUNDRY_NAME project name used in messages (default: RepoFoundry)
|
||||
@@ -66,8 +69,8 @@
|
||||
# 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
|
||||
# http, api, prompts, credentials, project, hosts, preflight, steps, plan,
|
||||
# repositories, mirror, git, localproject, framework, envfile, apply and cli. The files are loaded
|
||||
# from this directory only.
|
||||
#
|
||||
# Exit codes
|
||||
@@ -86,39 +89,18 @@ if ((BASH_VERSINFO[0] < 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] < 4)));
|
||||
fi
|
||||
|
||||
# Where this script lives; the library files and the project root are found
|
||||
# from here, never from the current directory. A link to the script (a
|
||||
# command in a folder on PATH) is followed to the real file, however many
|
||||
# links lie on the way; readlink without -f exists on Linux, macOS and Git
|
||||
# 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%/*}" ;;
|
||||
# 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 -P "$script_path_dir" && pwd -P)"
|
||||
SCRIPT_DIR="$(cd "$script_path_dir" && pwd)"
|
||||
readonly SCRIPT_DIR
|
||||
unset script_path script_path_dir script_link_count script_link_target
|
||||
# The script lives in src/; the checkout is one level up, next to
|
||||
# config.env.example and .env.example.
|
||||
PROJECT_ROOT="$(cd -P "$SCRIPT_DIR/.." && pwd -P)"
|
||||
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
|
||||
@@ -143,6 +125,8 @@ source "$SCRIPT_DIR/lib/http.sh"
|
||||
source "$SCRIPT_DIR/lib/api.sh"
|
||||
# shellcheck source=lib/prompts.sh
|
||||
source "$SCRIPT_DIR/lib/prompts.sh"
|
||||
# shellcheck source=lib/credentials.sh
|
||||
source "$SCRIPT_DIR/lib/credentials.sh"
|
||||
# shellcheck source=lib/project.sh
|
||||
source "$SCRIPT_DIR/lib/project.sh"
|
||||
# shellcheck source=lib/hosts.sh
|
||||
@@ -163,6 +147,8 @@ source "$SCRIPT_DIR/lib/git.sh"
|
||||
source "$SCRIPT_DIR/lib/localproject.sh"
|
||||
# shellcheck source=lib/framework.sh
|
||||
source "$SCRIPT_DIR/lib/framework.sh"
|
||||
# shellcheck source=lib/envfile.sh
|
||||
source "$SCRIPT_DIR/lib/envfile.sh"
|
||||
# shellcheck source=lib/apply.sh
|
||||
source "$SCRIPT_DIR/lib/apply.sh"
|
||||
# shellcheck source=lib/cli.sh
|
||||
@@ -184,14 +170,14 @@ main() {
|
||||
trap finish EXIT
|
||||
is_valid_repo_name "$PROJECT_NAME" ||
|
||||
die "REPOFOUNDRY_NAME is not a valid project name"
|
||||
WORKING_FOLDER="$(pwd -P)"
|
||||
parse_args "$@"
|
||||
check_tools
|
||||
setup_temp_dir
|
||||
load_configuration
|
||||
collect_credentials GITEA_TOKEN
|
||||
collect_project_details
|
||||
if ((PROJECT[has_github])); then
|
||||
require_github_credentials
|
||||
collect_credentials GITHUB_PAT GITHUB_USER
|
||||
fi
|
||||
init_steps
|
||||
print_summary
|
||||
|
||||
+3
-2
@@ -19,6 +19,7 @@ create_all() {
|
||||
add_framework
|
||||
install_framework
|
||||
copy_templates
|
||||
create_env_file
|
||||
}
|
||||
|
||||
# confirm_framework_access: the framework comes over SSH. Without SSH the
|
||||
@@ -47,9 +48,9 @@ confirm_reuse() {
|
||||
die "stopped: choose another name or remove the existing repository"
|
||||
fi
|
||||
done
|
||||
if ((${STATE[reuse_gitea]:-0})) && [[ -n ${PROJECT[license]} ]] &&
|
||||
if ((${STATE[reuse_gitea]:-0})) && ((PROJECT[has_github])) &&
|
||||
[[ ${STATE[gitea_repo]} == empty ]]; then
|
||||
warn "the empty Gitea repository is reused as it is: the ${PROJECT[license]} license is not added to it"
|
||||
warn "the empty Gitea repository is reused as it is: the $AGPL_LICENSE_KEY license is not added to it"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
usage() {
|
||||
cat <<EOF
|
||||
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
|
||||
EOF
|
||||
}
|
||||
|
||||
+14
-66
@@ -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, check_preset, check_preset_choice, validate_project_presets, 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, 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.
|
||||
@@ -142,23 +142,25 @@ validate_project_presets() {
|
||||
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
|
||||
check_preset PROJECT_LICENSE is_valid_license "$HINT_LICENSE"
|
||||
}
|
||||
|
||||
# validate_credentials: check the credentials that .env provides. A credential
|
||||
# that is not provided is not an error: it is asked later (collect_credentials).
|
||||
validate_credentials() {
|
||||
if [[ -z ${CREDENTIALS[GITEA_TOKEN]:-} ]]; then
|
||||
die "GITEA_TOKEN is missing in $ENV_FILE (see .env.example)"
|
||||
fi
|
||||
# Register secrets first so that no later message can show them.
|
||||
if [[ -n ${CREDENTIALS[GITEA_TOKEN]:-} ]]; then
|
||||
SECRET_VALUES+=("${CREDENTIALS[GITEA_TOKEN]}")
|
||||
fi
|
||||
if [[ -n ${CREDENTIALS[GITHUB_PAT]:-} ]]; then
|
||||
SECRET_VALUES+=("${CREDENTIALS[GITHUB_PAT]}")
|
||||
fi
|
||||
is_valid_token "${CREDENTIALS[GITEA_TOKEN]}" ||
|
||||
die "GITEA_TOKEN in $ENV_FILE is not a valid token (8 to 255 letters, digits or _ . ~ + / = -)"
|
||||
if [[ -n ${CREDENTIALS[GITEA_TOKEN]:-} ]] &&
|
||||
! is_valid_token "${CREDENTIALS[GITEA_TOKEN]}"; then
|
||||
die "GITEA_TOKEN in $ENV_FILE is not a valid token ($HINT_TOKEN)"
|
||||
fi
|
||||
if [[ -n ${CREDENTIALS[GITHUB_PAT]:-} ]] &&
|
||||
! is_valid_token "${CREDENTIALS[GITHUB_PAT]}"; then
|
||||
die "GITHUB_PAT in $ENV_FILE is not a valid token (8 to 255 letters, digits or _ . ~ + / = -)"
|
||||
die "GITHUB_PAT in $ENV_FILE is not a valid token ($HINT_TOKEN)"
|
||||
fi
|
||||
if [[ -n ${CREDENTIALS[GITHUB_USER]:-} ]] &&
|
||||
! is_valid_github_owner "${CREDENTIALS[GITHUB_USER]}"; then
|
||||
@@ -166,16 +168,6 @@ validate_credentials() {
|
||||
fi
|
||||
}
|
||||
|
||||
# GitHub credentials are only needed when the Maintainer chose GitHub.
|
||||
require_github_credentials() {
|
||||
local key
|
||||
for key in GITHUB_PAT GITHUB_USER; do
|
||||
if [[ -z ${CREDENTIALS[$key]:-} ]]; then
|
||||
die "GitHub was chosen but $key is missing in $ENV_FILE (see .env.example)"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
warn_if_env_unsafe() {
|
||||
local file="$1" dir mode
|
||||
case "$(uname -s 2>/dev/null || true)" in
|
||||
@@ -198,57 +190,13 @@ warn_if_env_unsafe() {
|
||||
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() {
|
||||
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
|
||||
validate_config
|
||||
confirm_folder_files
|
||||
# .env is optional: a credential it does not provide is asked.
|
||||
if [[ -e $ENV_FILE ]]; then
|
||||
parse_env_file "$ENV_FILE" CREDENTIAL_KEYS CREDENTIALS
|
||||
validate_credentials
|
||||
warn_if_env_unsafe "$ENV_FILE"
|
||||
fi
|
||||
validate_credentials
|
||||
}
|
||||
|
||||
+6
-12
@@ -18,7 +18,6 @@ readonly DEFAULT_MIRROR_INTERVAL="10m0s"
|
||||
readonly DEFAULT_SSH_PORT=10022
|
||||
readonly DEFAULT_FRAMEWORK_REPO="TirSystem/SQA-QC-Framework"
|
||||
readonly AGPL_LICENSE_KEY="AGPL-3.0"
|
||||
readonly NO_LICENSE_WORD="none"
|
||||
readonly DEFAULT_BRANCH="main"
|
||||
# What the prompts and the preset keys in config.env both tell the Maintainer
|
||||
# when a value is refused.
|
||||
@@ -26,25 +25,20 @@ readonly HINT_REPO_NAME="use letters, digits, '.', '_' or '-' (at most 100), not
|
||||
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_LICENSE="use a Gitea license key (letters, digits, '.', '+' or '-', at most 64), such as AGPL-3.0 or MIT, or none"
|
||||
readonly HINT_DIRECTORY="must not be empty, start with '-' or contain control characters"
|
||||
readonly HINT_TOKEN="8 to 255 letters, digits or _ . ~ + / = -"
|
||||
readonly ENV_FILE_NAME=".env"
|
||||
readonly PLAN_STEPS=("GitHub repository" "Gitea repository" "Push mirror"
|
||||
"Local project" "Framework" "Skills and hooks" "Templates")
|
||||
"Local project" "Framework" "Skills and hooks" "Templates" "Project .env")
|
||||
# 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
|
||||
PROJECT_NAME PROJECT_DESCRIPTION PROJECT_VISIBILITY GITEA_OWNER USE_GITHUB
|
||||
GITHUB_OWNER PROJECT_DIRECTORY ENABLE_PLAN_GATE PROJECT_LICENSE)
|
||||
GITHUB_OWNER PROJECT_DIRECTORY ENABLE_PLAN_GATE)
|
||||
readonly CREDENTIAL_KEYS=(GITHUB_PAT GITHUB_USER GITEA_TOKEN)
|
||||
|
||||
# The configuration files: named by --config and --env, or chosen by
|
||||
# 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=""
|
||||
CONFIG_FILE="$PROJECT_ROOT/config.env"
|
||||
ENV_FILE="$PROJECT_ROOT/.env"
|
||||
TMP_DIR=""
|
||||
HAS_JQ=0
|
||||
HTTP_STATUS=0
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# shellcheck shell=bash
|
||||
# shellcheck disable=SC2004,SC2034,SC2154 # shared state and arrays are declared in constants.sh
|
||||
# credentials.sh - Asking for a credential that .env does not provide.
|
||||
#
|
||||
# Part of create-project.sh: sourced by it, never run on its own.
|
||||
#
|
||||
# Provides: credential_label, collect_credentials
|
||||
|
||||
# credential_label KEY: the name of a credential as the Maintainer sees it.
|
||||
credential_label() {
|
||||
case "$1" in
|
||||
GITEA_TOKEN) printf 'Gitea access token' ;;
|
||||
GITHUB_PAT) printf 'GitHub personal access token' ;;
|
||||
GITHUB_USER) printf 'GitHub account name (the account the token belongs to)' ;;
|
||||
*) printf '%s' "$1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# collect_credentials KEY...: ask for each credential that is not already
|
||||
# provided. A token is read without echo and registered as a secret at once,
|
||||
# so no later message can show it; the GitHub account name is not secret and
|
||||
# is read like any other answer. An empty value in .env counts as not provided.
|
||||
collect_credentials() {
|
||||
local key
|
||||
for key in "$@"; do
|
||||
if [[ -n ${CREDENTIALS[$key]:-} ]]; then
|
||||
continue
|
||||
fi
|
||||
case "$key" in
|
||||
GITHUB_USER)
|
||||
prompt_value "$(credential_label "$key")" "" is_valid_github_owner \
|
||||
"use letters, digits or '-' (at most 39)"
|
||||
;;
|
||||
*)
|
||||
prompt_secret "$(credential_label "$key")" is_valid_token "$HINT_TOKEN"
|
||||
SECRET_VALUES+=("$REPLY")
|
||||
;;
|
||||
esac
|
||||
CREDENTIALS[$key]="$REPLY"
|
||||
REPLY=""
|
||||
done
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
# shellcheck shell=bash
|
||||
# shellcheck disable=SC2004,SC2034,SC2154 # shared state and arrays are declared in constants.sh
|
||||
# envfile.sh - The .env file of the new project: the one place a credential is written.
|
||||
#
|
||||
# Part of create-project.sh: sourced by it, never run on its own.
|
||||
#
|
||||
# Provides: env_file_keys, env_file_key_list, exclude_env_file, write_env_file, create_env_file
|
||||
|
||||
# env_file_keys: the credentials the new project needs, one per line: the
|
||||
# Gitea token, and the GitHub token and account name when GitHub was chosen.
|
||||
env_file_keys() {
|
||||
printf '%s\n' GITEA_TOKEN
|
||||
if ((PROJECT[has_github])); then
|
||||
printf '%s\n' GITHUB_PAT GITHUB_USER
|
||||
fi
|
||||
}
|
||||
|
||||
# env_file_key_list: the same keys on one line, for messages.
|
||||
env_file_key_list() {
|
||||
local keys
|
||||
keys="$(env_file_keys | tr '\n' ' ')"
|
||||
printf '%s' "${keys% }"
|
||||
}
|
||||
|
||||
# exclude_env_file DIR: make git ignore .env in DIR without touching a tracked
|
||||
# file: the entry goes into .git/info/exclude, which is never committed. It
|
||||
# does nothing when .env is already ignored.
|
||||
exclude_env_file() {
|
||||
local dir="$1" gitdir exclude
|
||||
if git_project "$dir" check-ignore -q -- "$ENV_FILE_NAME"; then
|
||||
return 0
|
||||
fi
|
||||
gitdir="$(git_project "$dir" rev-parse --absolute-git-dir)"
|
||||
exclude="$gitdir/info/exclude"
|
||||
mkdir -p -- "$gitdir/info"
|
||||
# Start on a fresh line when the file does not end with one.
|
||||
if [[ -s $exclude && -n "$(tail -c 1 -- "$exclude")" ]]; then
|
||||
printf '\n' >>"$exclude"
|
||||
fi
|
||||
printf '%s\n' "# RepoFoundry: the credentials file of this project" "$ENV_FILE_NAME" >>"$exclude"
|
||||
git_project "$dir" check-ignore -q -- "$ENV_FILE_NAME" ||
|
||||
die "could not make git ignore $ENV_FILE_NAME in $dir; nothing was written to it"
|
||||
}
|
||||
|
||||
# write_env_file DIR IS_REPLACE: write the credentials to DIR/.env. The file is
|
||||
# created private (mode 600) from the start, never readable by others, even
|
||||
# for a moment: it is written under umask 077 as a temporary file next to the
|
||||
# target and moved into place. An existing file is only replaced when
|
||||
# IS_REPLACE is 1, and a file that appears in the meantime is never replaced.
|
||||
write_env_file() {
|
||||
local dir="$1" is_replace="$2" target tmp key
|
||||
target="$dir/$ENV_FILE_NAME"
|
||||
tmp="$(umask 077 && mktemp "$dir/$ENV_FILE_NAME.XXXXXX")"
|
||||
TEMP_FILES+=("$tmp")
|
||||
{
|
||||
while IFS= read -r key; do
|
||||
printf '%s=%s\n' "$key" "${CREDENTIALS[$key]}"
|
||||
done < <(env_file_keys)
|
||||
} >"$tmp"
|
||||
if ((is_replace)); then
|
||||
mv -f -- "$tmp" "$target"
|
||||
else
|
||||
mv -n -- "$tmp" "$target"
|
||||
if [[ -e $tmp ]]; then
|
||||
die "$target appeared while it was being written; it was not replaced"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# create_env_file: the last step. Only after a yes (default no) is the .env
|
||||
# written, and an existing one is only replaced after another yes. Nothing
|
||||
# printed names a value, only the keys.
|
||||
create_env_file() {
|
||||
local label="Project .env" dir="${PROJECT[directory]}" keys is_replace=0
|
||||
keys="$(env_file_key_list)"
|
||||
begin_step "$label"
|
||||
prompt_yes_no "Create a $ENV_FILE_NAME file in the project with the credentials it needs ($keys); only you can read it and git ignores it" n
|
||||
if ! ((REPLY)); then
|
||||
finish_step "$label" "skipped" "(you declined)"
|
||||
return 0
|
||||
fi
|
||||
if git_project "$dir" ls-files --error-unmatch -- "$ENV_FILE_NAME" >/dev/null 2>&1; then
|
||||
finish_step "$label" "skipped" "($ENV_FILE_NAME is tracked by git; it was not written)"
|
||||
return 0
|
||||
fi
|
||||
if [[ -e $dir/$ENV_FILE_NAME || -L $dir/$ENV_FILE_NAME ]]; then
|
||||
prompt_yes_no "$ENV_FILE_NAME already exists in the project. Replace it" n
|
||||
if ! ((REPLY)); then
|
||||
finish_step "$label" "kept" "(the existing $ENV_FILE_NAME was left as it was)"
|
||||
return 0
|
||||
fi
|
||||
is_replace=1
|
||||
fi
|
||||
exclude_env_file "$dir"
|
||||
write_env_file "$dir" "$is_replace"
|
||||
finish_step "$label" "created" "($keys; only you can read it, git ignores it)"
|
||||
}
|
||||
+1
-15
@@ -4,7 +4,7 @@
|
||||
#
|
||||
# Part of create-project.sh: sourced by it, never run on its own.
|
||||
#
|
||||
# Provides: is_framework_skipped, init_framework_submodules, add_framework, run_framework_script, install_skills, install_hooks, install_framework, copy_template, copy_templates
|
||||
# Provides: is_framework_skipped, 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
|
||||
# SSH to Gitea is not available (the Maintainer agreed to that).
|
||||
@@ -12,18 +12,6 @@ is_framework_skipped() {
|
||||
[[ ${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
|
||||
# needed for it; a failure says how to test the access.
|
||||
add_framework() {
|
||||
@@ -39,7 +27,6 @@ add_framework() {
|
||||
if [[ $existing != "$url" ]]; then
|
||||
die "'framework' already exists in $dir and is not the framework submodule ($url)"
|
||||
fi
|
||||
init_framework_submodules "$dir"
|
||||
finish_step "$label" "reused" "$url (already a submodule)"
|
||||
return 0
|
||||
fi
|
||||
@@ -48,7 +35,6 @@ add_framework() {
|
||||
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' ' ')"
|
||||
fi
|
||||
init_framework_submodules "$dir"
|
||||
finish_step "$label" "created" "$url"
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ checkout_gitea_history() {
|
||||
}
|
||||
|
||||
# create_local_project: the directory, the git repository on main, the
|
||||
# remotes and, when a license applies, the license history. No commit is made.
|
||||
# remotes and, when GitHub is chosen, the license history. No commit is made.
|
||||
create_local_project() {
|
||||
local label="Local project" dir="${PROJECT[directory]}"
|
||||
begin_step "$label"
|
||||
@@ -84,8 +84,6 @@ create_local_project() {
|
||||
ensure_remote "$dir" origin "$(origin_url)"
|
||||
if ((PROJECT[has_github])); then
|
||||
ensure_remote "$dir" github "$(github_remote_url)"
|
||||
fi
|
||||
if [[ -n ${PROJECT[license]} ]]; then
|
||||
checkout_gitea_history "$dir"
|
||||
fi
|
||||
finish_step "$label" "created" "$dir (origin over $(origin_protocol))"
|
||||
|
||||
+3
-2
@@ -22,8 +22,8 @@ print_plan() {
|
||||
say "Plan:"
|
||||
if ((STATE[reuse_gitea])); then
|
||||
gitea_action="reuse the existing repository (you will be asked to confirm)"
|
||||
elif [[ -n ${PROJECT[license]} ]]; then
|
||||
gitea_action="create (${PROJECT[visibility]}) with the ${PROJECT[license]} license$(license_note)"
|
||||
elif ((PROJECT[has_github])); then
|
||||
gitea_action="create (${PROJECT[visibility]}) with the $AGPL_LICENSE_KEY license"
|
||||
else
|
||||
gitea_action="create (${PROJECT[visibility]}), empty"
|
||||
fi
|
||||
@@ -54,4 +54,5 @@ print_plan() {
|
||||
else
|
||||
say "$(printf ' %-18s: %s' "Framework" "NOT possible without SSH to Gitea; you will be asked whether to go on without it")"
|
||||
fi
|
||||
say "$(printf ' %-18s: %s' "Project .env" "you are asked whether to create it ($(env_file_key_list))")"
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ check_gitea_organization() {
|
||||
check_gitea_license() {
|
||||
api_call gitea GET /licenses
|
||||
expect_status "cannot list the licenses of the Gitea server" 200
|
||||
json_has_value "$HTTP_BODY_FILE" key "${PROJECT[license]}" ||
|
||||
die "the Gitea server does not offer the ${PROJECT[license]} license"
|
||||
json_has_value "$HTTP_BODY_FILE" key "$AGPL_LICENSE_KEY" ||
|
||||
die "the Gitea server does not offer the $AGPL_LICENSE_KEY license"
|
||||
}
|
||||
|
||||
# inspect_repository HOST: record in STATE[HOST_repo] whether the repository
|
||||
@@ -70,7 +70,7 @@ preflight_gitea() {
|
||||
check_gitea_organization "$owner" "$login"
|
||||
STATE[gitea_owner_kind]="organization"
|
||||
fi
|
||||
if [[ -n ${PROJECT[license]} ]]; then
|
||||
if ((PROJECT[has_github])); then
|
||||
check_gitea_license
|
||||
fi
|
||||
inspect_repository gitea
|
||||
@@ -143,7 +143,7 @@ decide_existing_repositories() {
|
||||
free) ;;
|
||||
empty) STATE[reuse_$host]=1 ;;
|
||||
initial_only)
|
||||
if [[ $host == gitea && -n ${PROJECT[license]} ]]; then
|
||||
if [[ $host == gitea ]] && ((PROJECT[has_github])); then
|
||||
STATE[reuse_$host]=1
|
||||
else
|
||||
die "the $(host_label "$host") repository $owner/${PROJECT[name]} already exists and has content; choose another name or remove it first"
|
||||
|
||||
+3
-32
@@ -4,7 +4,7 @@
|
||||
#
|
||||
# Part of create-project.sh: sourced by it, never run on its own.
|
||||
#
|
||||
# Provides: preset_detail, resolve_license, license_note, collect_project_details, collect_github_details, source_note, 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].
|
||||
@@ -14,33 +14,6 @@ preset_detail() {
|
||||
PRESET[$2]=1
|
||||
}
|
||||
|
||||
# resolve_license: the license that applies, in PROJECT[license] (empty means
|
||||
# none). PROJECT_LICENSE in config.env decides, and "none" means no license;
|
||||
# without it AGPL-3.0 applies only when GitHub is chosen and the project is
|
||||
# public. The license is never asked.
|
||||
resolve_license() {
|
||||
PROJECT[license]=""
|
||||
if [[ -n ${CONFIG[PROJECT_LICENSE]+set} ]]; then
|
||||
PRESET[license]=1
|
||||
if [[ ${CONFIG[PROJECT_LICENSE],,} != "$NO_LICENSE_WORD" ]]; then
|
||||
PROJECT[license]="${CONFIG[PROJECT_LICENSE]}"
|
||||
fi
|
||||
elif ((PROJECT[has_github])) && [[ ${PROJECT[visibility]} == public ]]; then
|
||||
PROJECT[license]="$AGPL_LICENSE_KEY"
|
||||
fi
|
||||
}
|
||||
|
||||
# license_note: where the license on the Gitea repository comes from.
|
||||
license_note() {
|
||||
if [[ -n ${PRESET[license]:-} ]]; then
|
||||
source_note license
|
||||
elif [[ -n ${PROJECT[license]} ]]; then
|
||||
printf ' (default: GitHub and a public project)'
|
||||
elif ((PROJECT[has_github])); then
|
||||
printf ' (no default for a private project)'
|
||||
fi
|
||||
}
|
||||
|
||||
# Ask for each project detail, except those set in config.env.
|
||||
collect_project_details() {
|
||||
preset_detail PROJECT_NAME name ||
|
||||
@@ -56,7 +29,6 @@ collect_project_details() {
|
||||
prompt_value "Gitea owner (user or organization)" "" is_valid_gitea_owner "$HINT_GITEA_OWNER"
|
||||
PROJECT[gitea_owner]="$REPLY"
|
||||
collect_github_details
|
||||
resolve_license
|
||||
preset_detail PROJECT_DIRECTORY directory ||
|
||||
prompt_value "Local directory" "./${PROJECT[name]}" is_valid_directory "$HINT_DIRECTORY"
|
||||
PROJECT[directory]="$REPLY"
|
||||
@@ -74,7 +46,7 @@ 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" y
|
||||
prompt_yes_no "Also create a GitHub repository (applies the AGPL license)" y
|
||||
fi
|
||||
PROJECT[has_github]="$REPLY"
|
||||
PROJECT[github_owner]=""
|
||||
@@ -118,11 +90,10 @@ print_summary() {
|
||||
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]}$(source_note github_owner)"
|
||||
say " GitHub : ${CONFIG[GITHUB_WEB_URL]}/${PROJECT[github_owner]}/${PROJECT[name]} (AGPL license applied)$(source_note github_owner)"
|
||||
else
|
||||
say " GitHub : not used$(source_note has_github)"
|
||||
fi
|
||||
say " License : ${PROJECT[license]:-none}$(license_note)"
|
||||
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)," \
|
||||
|
||||
+22
-1
@@ -4,7 +4,7 @@
|
||||
#
|
||||
# Part of create-project.sh: sourced by it, never run on its own.
|
||||
#
|
||||
# Provides: prompt_value, prompt_choice, prompt_yes_no
|
||||
# Provides: prompt_value, prompt_secret, prompt_choice, prompt_yes_no
|
||||
|
||||
# prompt_value LABEL DEFAULT VALIDATOR HINT: ask until VALIDATOR accepts the
|
||||
# answer; the accepted answer is returned in REPLY.
|
||||
@@ -27,6 +27,27 @@ prompt_value() {
|
||||
done
|
||||
}
|
||||
|
||||
# prompt_secret LABEL VALIDATOR HINT: like prompt_value for a secret. What is
|
||||
# typed is not shown (read -s) and a refused answer is never repeated in the
|
||||
# message. An empty answer is refused; there is no default.
|
||||
prompt_secret() {
|
||||
local label="$1" validator="$2" hint="$3" answer
|
||||
while true; do
|
||||
printf '%s (input is hidden): ' "$label" >&2
|
||||
IFS= read -rs answer || {
|
||||
printf '\n' >&2
|
||||
die "no input available for '$label'"
|
||||
}
|
||||
printf '\n' >&2 # the newline that hidden input did not echo
|
||||
answer="$(trim "$answer")"
|
||||
if [[ -n $answer ]] && "$validator" "$answer"; then
|
||||
REPLY="$answer"
|
||||
return 0
|
||||
fi
|
||||
warn "invalid $label: $hint"
|
||||
done
|
||||
}
|
||||
|
||||
# prompt_choice LABEL DEFAULT CHOICE...: the answer is returned in REPLY.
|
||||
prompt_choice() {
|
||||
local label="$1" default="$2" answer
|
||||
|
||||
@@ -18,8 +18,8 @@ repo_body() {
|
||||
"${PROJECT[name]}" "$description" "$private"
|
||||
return 0
|
||||
fi
|
||||
if [[ -n ${PROJECT[license]} ]]; then
|
||||
extra=',"auto_init":true,"license":"'"${PROJECT[license]}"'"'
|
||||
if ((PROJECT[has_github])); then
|
||||
extra=',"auto_init":true,"license":"'"$AGPL_LICENSE_KEY"'"'
|
||||
else
|
||||
extra=',"auto_init":false'
|
||||
fi
|
||||
|
||||
@@ -58,11 +58,6 @@ is_valid_port() {
|
||||
[[ $1 =~ ^[0-9]{1,5}$ ]] && ((10#$1 >= 1 && 10#$1 <= 65535))
|
||||
}
|
||||
|
||||
# A Gitea license key such as AGPL-3.0 or MIT, or the word none.
|
||||
is_valid_license() {
|
||||
[[ -n $1 && ${#1} -le 64 && $1 =~ ^[A-Za-z0-9.+-]+$ ]]
|
||||
}
|
||||
|
||||
# A Go duration such as 10m0s or 8h0m0s, the form Gitea expects.
|
||||
is_valid_interval() {
|
||||
[[ -n $1 && $1 =~ ^([0-9]+h)?([0-9]+m)?([0-9]+s)?$ ]]
|
||||
|
||||
+2
-16
@@ -21,16 +21,6 @@ readonly FAKE_GITHUB_PAT="ghpFAKEtoken1234567890"
|
||||
# Answers to the prompts: name, description, visibility, Gitea owner, GitHub
|
||||
# yes or no, GitHub owner, directory, plan gate.
|
||||
readonly ANSWERS_GITHUB=$'my-app\nA test app\n\nTirSystem\ny\nacme-org\n\nn\n'
|
||||
# The same, for a public project (the AGPL-3.0 default applies with GitHub).
|
||||
readonly ANSWERS_GITHUB_PUBLIC=$'my-app
|
||||
A test app
|
||||
public
|
||||
TirSystem
|
||||
y
|
||||
acme-org
|
||||
|
||||
n
|
||||
'
|
||||
readonly ANSWERS_GITEA_ONLY=$'my-app\n\n\nTirSystem\nn\n\nn\n'
|
||||
|
||||
SHARED_REMOTES=""
|
||||
@@ -120,15 +110,12 @@ write_gitconfig() {
|
||||
insteadOf = https://git.example.test/
|
||||
[url "file://$WORK/remote/"]
|
||||
insteadOf = ssh://git@git.example.test:10022/
|
||||
[url "file://$WORK/remote/"]
|
||||
insteadOf = ssh://git@git.tirsystem.com:10022/
|
||||
EOF
|
||||
}
|
||||
|
||||
# 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
|
||||
# license commit), for the framework and for the checklists the framework
|
||||
# holds as its own submodule (copies of the real ones).
|
||||
# license commit) and for the framework (a copy of the real one).
|
||||
ensure_shared_remotes() {
|
||||
if [[ -n $SHARED_REMOTES && -d $SHARED_REMOTES ]]; then
|
||||
return 0
|
||||
@@ -136,7 +123,6 @@ ensure_shared_remotes() {
|
||||
SHARED_REMOTES="$(mktemp -d "${TMPDIR:-/tmp}/repofoundry-remotes.XXXXXX")"
|
||||
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/qc" "$SHARED_REMOTES/TirSystem/SQA-QC-Checklists.git"
|
||||
git init -q --bare "$SHARED_REMOTES/TirSystem/my-app.git"
|
||||
git init -q "$SHARED_REMOTES/seed"
|
||||
git -C "$SHARED_REMOTES/seed" symbolic-ref HEAD refs/heads/main
|
||||
@@ -167,7 +153,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 -o -type l \) -delete
|
||||
find "$WORK" \( -type f -o -type p \) -delete
|
||||
find "$WORK" -depth -type d -exec rmdir {} +
|
||||
fi
|
||||
WORK=""
|
||||
|
||||
@@ -96,7 +96,6 @@ validate_credentials"
|
||||
assert_status "$case_name" 1 "$STATUS"
|
||||
assert_contains "$case_name message" "$ERR" "$expected"
|
||||
done <<'EOF'
|
||||
no Gitea token|GITHUB_USER=octo\n|GITEA_TOKEN is missing
|
||||
token too short|GITEA_TOKEN=short\n|not a valid token
|
||||
token with a backslash|GITEA_TOKEN=abc\\defgh12345\n|not a valid token
|
||||
bad GitHub token|GITEA_TOKEN=abcdefgh12345\nGITHUB_PAT=bad token\n|GITHUB_PAT
|
||||
@@ -104,18 +103,6 @@ bad GitHub user|GITEA_TOKEN=abcdefgh12345\nGITHUB_USER=-bad-\n|GITHUB_USER
|
||||
EOF
|
||||
}
|
||||
|
||||
test_github_credentials_required_only_when_chosen() {
|
||||
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$WORK/e.env"
|
||||
run_lib "" "parse_env_file \"$WORK/e.env\" CREDENTIAL_KEYS CREDENTIALS
|
||||
validate_credentials
|
||||
echo no-github-ok
|
||||
ENV_FILE=\"$WORK/e.env\"
|
||||
require_github_credentials"
|
||||
assert_contains "Gitea-only .env is valid" "$OUT" "no-github-ok"
|
||||
assert_status "GitHub credentials missing" 1 "$STATUS"
|
||||
assert_contains "names the missing key" "$ERR" "GITHUB_PAT is missing"
|
||||
}
|
||||
|
||||
test_validators() {
|
||||
local fn value expected
|
||||
while IFS='|' read -r fn value expected; do
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
#!/usr/bin/env bash
|
||||
# test-credentials.sh - tests for the credentials that .env does not provide
|
||||
# and for the .env file of the new project (MIL-005): they are asked without
|
||||
# echo, the project .env is only written after a yes, owner-only and ignored
|
||||
# by git, and no token appears anywhere else. Sourced by run-tests.sh.
|
||||
|
||||
# shellcheck disable=SC2016 # snippet and fixture text is literal on purpose
|
||||
|
||||
readonly NL=$'\n'
|
||||
|
||||
# credentials_input: the answers of a run with no .env at all and GitHub
|
||||
# chosen: the Gitea token, the details, then the GitHub token and account.
|
||||
credentials_input() {
|
||||
printf '%s' "$FAKE_GITEA_TOKEN$NL$ANSWERS_GITHUB$FAKE_GITHUB_PAT${NL}octo-user$NL"
|
||||
}
|
||||
|
||||
run_dry_cred() {
|
||||
run_cli "$1" --config "$WORK/config.env" --env "$WORK/.env"
|
||||
}
|
||||
|
||||
run_apply_cred() {
|
||||
run_cli "$1" --apply --config "$WORK/config.env" --env "$WORK/.env"
|
||||
}
|
||||
|
||||
# without_env: remove the .env of the fixtures.
|
||||
without_env() {
|
||||
rm -f -- "$WORK/.env"
|
||||
}
|
||||
|
||||
# env_mode FILE: the permission bits, empty where the platform has none.
|
||||
env_mode() {
|
||||
case "$(uname -s 2>/dev/null || true)" in
|
||||
MINGW* | MSYS* | CYGWIN*) ;;
|
||||
*) stat -c '%a' -- "$1" 2>/dev/null || stat -f '%Lp' -- "$1" 2>/dev/null || true ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------ prompt_secret
|
||||
|
||||
test_prompt_secret_asks_again_and_never_repeats_the_answer() {
|
||||
run_lib $'short\n\nlongenoughtoken1\n' \
|
||||
'prompt_secret "Gitea access token" is_valid_token "needs 8 characters"; echo "[$REPLY]"'
|
||||
assert_status "valid answer found" 0 "$STATUS"
|
||||
assert_eq "valid answer returned" "[longenoughtoken1]" "$OUT"
|
||||
assert_contains "told why" "$ERR" "invalid Gitea access token: needs 8 characters"
|
||||
assert_not_contains "refused answer not repeated" "$ERR" "short"
|
||||
assert_contains "says the input is hidden" "$ERR" "(input is hidden)"
|
||||
}
|
||||
|
||||
test_prompt_secret_stops_when_input_ends() {
|
||||
run_lib "" 'prompt_secret "Gitea access token" is_valid_token "x" </dev/null'
|
||||
assert_status "end of input" 1 "$STATUS"
|
||||
assert_contains "message names the credential" "$ERR" "no input available for 'Gitea access token'"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------ collect_credentials
|
||||
|
||||
test_collect_credentials_asks_only_what_is_missing() {
|
||||
run_lib "$FAKE_GITHUB_PAT${NL}octo-user$NL" \
|
||||
'CREDENTIALS[GITEA_TOKEN]=giteaFAKEtoken1234567890
|
||||
collect_credentials GITEA_TOKEN GITHUB_PAT GITHUB_USER
|
||||
printf "%s|%s\n" "${CREDENTIALS[GITHUB_USER]}" "${#SECRET_VALUES[@]}"'
|
||||
assert_status "asked" 0 "$STATUS"
|
||||
assert_eq "account name kept, one secret registered" "octo-user|1" "$OUT"
|
||||
assert_contains "GitHub token asked" "$ERR" "GitHub personal access token"
|
||||
assert_contains "account asked" "$ERR" "GitHub account name"
|
||||
assert_not_contains "Gitea token not asked" "$ERR" "Gitea access token"
|
||||
assert_not_contains "no token shown" "$ERR$OUT" "$FAKE_GITHUB_PAT"
|
||||
}
|
||||
|
||||
test_a_token_that_is_asked_is_registered_as_a_secret() {
|
||||
run_lib "$FAKE_GITEA_TOKEN$NL" \
|
||||
'collect_credentials GITEA_TOKEN
|
||||
warn "the token is giteaFAKEtoken1234567890 here"'
|
||||
assert_status "asked" 0 "$STATUS"
|
||||
assert_not_contains "redacted in later messages" "$ERR" "$FAKE_GITEA_TOKEN"
|
||||
}
|
||||
|
||||
test_an_empty_value_in_env_counts_as_not_provided() {
|
||||
printf 'GITEA_TOKEN=\nGITHUB_USER=\n' >"$WORK/e.env"
|
||||
run_lib "$FAKE_GITEA_TOKEN$NL" \
|
||||
'parse_env_file "'"$WORK"'/e.env" CREDENTIAL_KEYS CREDENTIALS
|
||||
validate_credentials
|
||||
collect_credentials GITEA_TOKEN
|
||||
echo asked-ok'
|
||||
assert_status "empty value tolerated" 0 "$STATUS"
|
||||
assert_contains "asked instead" "$ERR" "Gitea access token"
|
||||
}
|
||||
|
||||
test_validate_credentials_no_longer_requires_any() {
|
||||
printf '# nothing\n' >"$WORK/e.env"
|
||||
run_lib "" 'parse_env_file "'"$WORK"'/e.env" CREDENTIAL_KEYS CREDENTIALS
|
||||
validate_credentials
|
||||
echo fine'
|
||||
assert_status "no credential required" 0 "$STATUS"
|
||||
assert_eq "no error" "fine" "$OUT"
|
||||
printf 'GITEA_TOKEN=short\n' >"$WORK/e.env"
|
||||
run_lib "" 'parse_env_file "'"$WORK"'/e.env" CREDENTIAL_KEYS CREDENTIALS
|
||||
validate_credentials'
|
||||
assert_status "a provided bad token is still refused" 1 "$STATUS"
|
||||
assert_contains "named" "$ERR" "GITEA_TOKEN"
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------- the run
|
||||
|
||||
test_env_is_optional_and_the_token_is_asked_without_echo() {
|
||||
setup_hosts
|
||||
without_env
|
||||
run_dry_cred "$FAKE_GITEA_TOKEN$NL$ANSWERS_GITEA_ONLY"
|
||||
assert_status "dry run without .env" 0 "$STATUS"
|
||||
assert_contains "token asked" "$ERR" "Gitea access token (input is hidden)"
|
||||
assert_contains "plan printed" "$OUT" "Plan:"
|
||||
assert_not_contains "token not shown" "$OUT$ERR" "$FAKE_GITEA_TOKEN"
|
||||
assert_not_contains "only reads" "$(calls)" "POST"
|
||||
}
|
||||
|
||||
test_a_provided_credential_is_not_asked() {
|
||||
setup_hosts
|
||||
run_dry_cred "$ANSWERS_GITHUB"
|
||||
assert_status "all provided" 0 "$STATUS"
|
||||
assert_not_contains "no token prompt" "$ERR" "(input is hidden)"
|
||||
assert_not_contains "no account prompt" "$ERR" "GitHub account name"
|
||||
}
|
||||
|
||||
test_github_credentials_are_asked_only_when_github_is_chosen() {
|
||||
setup_hosts
|
||||
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$WORK/.env"
|
||||
run_dry_cred "$ANSWERS_GITEA_ONLY"
|
||||
assert_status "Gitea only" 0 "$STATUS"
|
||||
assert_not_contains "no GitHub token asked" "$ERR" "GitHub personal access token"
|
||||
assert_not_contains "no GitHub account asked" "$ERR" "GitHub account name"
|
||||
run_dry_cred "$ANSWERS_GITHUB$FAKE_GITHUB_PAT${NL}octo-user$NL"
|
||||
assert_status "GitHub chosen" 0 "$STATUS"
|
||||
assert_contains "GitHub token asked" "$ERR" "GitHub personal access token (input is hidden)"
|
||||
assert_contains "GitHub account asked" "$ERR" "GitHub account name"
|
||||
assert_not_contains "token not shown" "$OUT$ERR" "$FAKE_GITHUB_PAT"
|
||||
}
|
||||
|
||||
test_an_invalid_asked_value_is_asked_again_and_never_shown() {
|
||||
setup_hosts
|
||||
without_env
|
||||
run_dry_cred "bad token${NL}$FAKE_GITEA_TOKEN$NL$ANSWERS_GITEA_ONLY"
|
||||
assert_status "second answer accepted" 0 "$STATUS"
|
||||
assert_contains "told it is invalid" "$ERR" "invalid Gitea access token"
|
||||
assert_not_contains "refused value not shown" "$ERR$OUT" "bad token"
|
||||
}
|
||||
|
||||
test_input_that_ends_stops_before_any_request() {
|
||||
setup_hosts
|
||||
without_env
|
||||
run_dry_cred ""
|
||||
assert_status "stopped" 1 "$STATUS"
|
||||
assert_contains "key named" "$ERR" "no input available for 'Gitea access token'"
|
||||
assert_eq "no request made" "" "$(calls)"
|
||||
assert_not_contains "no creation report" "$OUT" "This is what exists now"
|
||||
}
|
||||
|
||||
test_asked_tokens_do_not_leak_under_bash_x() {
|
||||
setup_hosts
|
||||
without_env
|
||||
STATUS=0
|
||||
PATH="$WORK/bin:$PATH" STUB_DIR="$WORK" TMPDIR="$WORK/tmp" "$BASH" -x "$SCRIPT" \
|
||||
--config "$WORK/config.env" --env "$WORK/.env" <<<"$(credentials_input)" \
|
||||
>"$WORK/out.txt" 2>"$WORK/err.txt" || STATUS=$?
|
||||
assert_status "run under bash -x" 0 "$STATUS"
|
||||
assert_not_contains "no Gitea token in the trace" "$(cat "$WORK/err.txt" "$WORK/out.txt")" "$FAKE_GITEA_TOKEN"
|
||||
assert_not_contains "no GitHub token in the trace" "$(cat "$WORK/err.txt" "$WORK/out.txt")" "$FAKE_GITHUB_PAT"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------- the project .env
|
||||
|
||||
test_the_dry_run_names_the_env_step_and_writes_nothing() {
|
||||
setup_hosts
|
||||
run_dry_cred "$ANSWERS_GITHUB"
|
||||
assert_contains "plan line" "$OUT" "Project .env : you are asked whether to create it (GITEA_TOKEN GITHUB_PAT GITHUB_USER)"
|
||||
assert_file_missing "no .env" "$WORK/my-app/.env"
|
||||
}
|
||||
|
||||
test_the_project_env_is_not_created_without_a_yes() {
|
||||
setup_hosts
|
||||
run_apply_cred "${ANSWERS_GITHUB}y$NL$NL"
|
||||
assert_status "run" 0 "$STATUS"
|
||||
assert_file_missing "default is no" "$WORK/my-app/.env"
|
||||
assert_contains "reported" "$OUT" "Project .env : skipped (you declined)"
|
||||
setup_hosts
|
||||
run_apply_cred "${ANSWERS_GITHUB}y${NL}n$NL"
|
||||
assert_file_missing "explicit no" "$WORK/my-app/.env"
|
||||
}
|
||||
|
||||
test_the_project_env_holds_only_the_needed_keys_and_is_private() {
|
||||
setup_hosts
|
||||
run_apply_cred "${ANSWERS_GITHUB}y${NL}y$NL"
|
||||
assert_status "run" 0 "$STATUS"
|
||||
assert_file_exists ".env created" "$WORK/my-app/.env"
|
||||
assert_eq "exactly the needed keys" "GITEA_TOKEN=$FAKE_GITEA_TOKEN${NL}GITHUB_PAT=$FAKE_GITHUB_PAT${NL}GITHUB_USER=octo-user" "$(cat "$WORK/my-app/.env")"
|
||||
assert_eq "owner-only" "$(env_mode "$WORK/my-app/.env")" "$([[ -z "$(env_mode "$WORK/my-app/.env")" ]] || echo 600)"
|
||||
assert_contains "reported with the keys, not the values" "$OUT" "Project .env : created (GITEA_TOKEN GITHUB_PAT GITHUB_USER;"
|
||||
assert_not_contains "no token in the output" "$OUT$ERR" "$FAKE_GITEA_TOKEN"
|
||||
assert_not_contains "no GitHub token in the output" "$OUT$ERR" "$FAKE_GITHUB_PAT"
|
||||
# Git ignores it without any tracked file changing.
|
||||
check
|
||||
if ! git -C "$WORK/my-app" check-ignore -q -- .env; then
|
||||
fail ".env is not ignored by git"
|
||||
fi
|
||||
assert_not_contains "not listed by git status" "$(git -C "$WORK/my-app" status --porcelain)" ".env"
|
||||
assert_contains "excluded locally" "$(cat "$WORK/my-app/.git/info/exclude")" ".env"
|
||||
check
|
||||
if [[ -e $WORK/my-app/.gitignore ]]; then
|
||||
fail "a .gitignore was written; only .git/info/exclude may change"
|
||||
fi
|
||||
# No temporary file is left behind.
|
||||
assert_eq "no leftover file" "" "$(find "$WORK/my-app" -maxdepth 1 -name '.env.*' -print)"
|
||||
}
|
||||
|
||||
test_the_project_env_without_github_holds_only_the_gitea_token() {
|
||||
setup_hosts
|
||||
run_apply_cred "${ANSWERS_GITEA_ONLY}y${NL}y$NL"
|
||||
assert_status "run" 0 "$STATUS"
|
||||
assert_eq "one key" "GITEA_TOKEN=$FAKE_GITEA_TOKEN" "$(cat "$WORK/my-app/.env")"
|
||||
assert_contains "reported" "$OUT" "Project .env : created (GITEA_TOKEN;"
|
||||
}
|
||||
|
||||
test_asked_credentials_are_what_the_project_env_holds() {
|
||||
setup_hosts
|
||||
without_env
|
||||
run_apply_cred "$(credentials_input)${NL}y${NL}y$NL"
|
||||
assert_status "run" 0 "$STATUS"
|
||||
assert_eq "the asked values" "GITEA_TOKEN=$FAKE_GITEA_TOKEN${NL}GITHUB_PAT=$FAKE_GITHUB_PAT${NL}GITHUB_USER=octo-user" "$(cat "$WORK/my-app/.env")"
|
||||
assert_not_contains "no token in the output" "$OUT$ERR" "$FAKE_GITEA_TOKEN"
|
||||
}
|
||||
|
||||
test_no_token_is_in_any_file_but_the_project_env() {
|
||||
setup_hosts
|
||||
without_env
|
||||
run_apply_cred "$(credentials_input)${NL}y${NL}y$NL"
|
||||
assert_status "run" 0 "$STATUS"
|
||||
local hits
|
||||
# The new project (with its .git folder), the script's temporary directory
|
||||
# and its output; the stub curl's own request log is not part of the product.
|
||||
hits="$(grep -rIl -F -e "$FAKE_GITEA_TOKEN" -e "$FAKE_GITHUB_PAT" "$WORK/my-app" "$WORK/tmp" "$WORK/out.txt" "$WORK/err.txt" 2>/dev/null |
|
||||
grep -v -e '/my-app/\.env$' || true)"
|
||||
assert_eq "only the project .env holds a token" "" "$hits"
|
||||
}
|
||||
|
||||
test_an_existing_env_is_kept_unless_the_maintainer_says_replace() {
|
||||
setup_hosts
|
||||
mkdir -p "$WORK/my-app"
|
||||
printf 'keep\n' >"$WORK/my-app/.env"
|
||||
run_apply_cred "${ANSWERS_GITHUB}y${NL}y${NL}y${NL}n$NL"
|
||||
assert_status "declined replacing" 0 "$STATUS"
|
||||
assert_eq "unchanged" "keep" "$(cat "$WORK/my-app/.env")"
|
||||
assert_contains "reported" "$OUT" "Project .env : kept (the existing .env was left as it was)"
|
||||
remove_workdir
|
||||
new_workdir
|
||||
setup_hosts
|
||||
mkdir -p "$WORK/my-app"
|
||||
printf 'keep\n' >"$WORK/my-app/.env"
|
||||
run_apply_cred "${ANSWERS_GITHUB}y${NL}y${NL}y${NL}y$NL"
|
||||
assert_status "agreed to replace" 0 "$STATUS"
|
||||
assert_contains "replaced" "$(cat "$WORK/my-app/.env")" "GITEA_TOKEN=$FAKE_GITEA_TOKEN"
|
||||
assert_eq "private after replacing" "$(env_mode "$WORK/my-app/.env")" "$([[ -z "$(env_mode "$WORK/my-app/.env")" ]] || echo 600)"
|
||||
}
|
||||
|
||||
test_a_tracked_env_is_never_written() {
|
||||
mkdir -p "$WORK/p"
|
||||
git -C "$WORK/p" init -q
|
||||
printf 'tracked\n' >"$WORK/p/.env"
|
||||
git -C "$WORK/p" add .env
|
||||
git -C "$WORK/p" -c user.name=t -c user.email=t@example.test commit -q -m init
|
||||
run_lib "y$NL" \
|
||||
'PROJECT[directory]="'"$WORK"'/p"; PROJECT[has_github]=0
|
||||
CREDENTIALS[GITEA_TOKEN]=giteaFAKEtoken1234567890
|
||||
init_steps
|
||||
create_env_file
|
||||
echo "${STEP_STATUS["Project .env"]}"'
|
||||
assert_status "run" 0 "$STATUS"
|
||||
assert_eq "skipped" "skipped" "$OUT"
|
||||
assert_eq "unchanged" "tracked" "$(cat "$WORK/p/.env")"
|
||||
assert_contains "says why" "$(cat "$WORK/err.txt" "$WORK/out.txt")" "skipped"
|
||||
}
|
||||
|
||||
test_exclude_is_added_once_and_keeps_the_existing_entries() {
|
||||
mkdir -p "$WORK/p"
|
||||
git -C "$WORK/p" init -q
|
||||
printf 'build/' >"$WORK/p/.git/info/exclude" # no trailing newline
|
||||
run_lib "" \
|
||||
'exclude_env_file "'"$WORK"'/p"
|
||||
exclude_env_file "'"$WORK"'/p"
|
||||
echo done'
|
||||
assert_status "run" 0 "$STATUS"
|
||||
local exclude
|
||||
exclude="$(cat "$WORK/p/.git/info/exclude")"
|
||||
assert_contains "old entry kept" "$exclude" "build/"
|
||||
assert_eq "entry added once" "1" "$(grep -c '^\.env$' "$WORK/p/.git/info/exclude")"
|
||||
assert_eq "old entry still on its own line" "1" "$(grep -c '^build/$' "$WORK/p/.git/info/exclude")"
|
||||
}
|
||||
|
||||
test_exclude_does_nothing_when_env_is_already_ignored() {
|
||||
mkdir -p "$WORK/p"
|
||||
git -C "$WORK/p" init -q
|
||||
printf '.env\n' >"$WORK/p/.gitignore"
|
||||
run_lib "" 'exclude_env_file "'"$WORK"'/p"; echo done'
|
||||
assert_status "run" 0 "$STATUS"
|
||||
assert_eq "exclude file untouched" "0" "$(grep -c '^\.env$' "$WORK/p/.git/info/exclude" || true)"
|
||||
}
|
||||
+9
-23
@@ -31,7 +31,7 @@ test_dry_run_prints_the_plan_and_only_reads() {
|
||||
setup_hosts
|
||||
run_dry "$ANSWERS_GITHUB"
|
||||
assert_status "dry run" 0 "$STATUS"
|
||||
assert_contains "Gitea plan" "$OUT" "Gitea repository : create (private), empty https://git.example.test/TirSystem/my-app"
|
||||
assert_contains "Gitea plan" "$OUT" "Gitea repository : create (private) with the AGPL-3.0 license https://git.example.test/TirSystem/my-app"
|
||||
assert_contains "GitHub plan" "$OUT" "GitHub repository : create (private), empty https://github.com/acme-org/my-app"
|
||||
assert_contains "mirror plan" "$OUT" "Push mirror : Gitea -> GitHub every 10m0s"
|
||||
assert_contains "origin plan" "$OUT" "Local origin : will use SSH (the SSH test passed)"
|
||||
@@ -148,18 +148,14 @@ test_github_owner_must_be_a_member() {
|
||||
assert_contains "pending message" "$ERR" "membership of the GitHub organization 'acme-org' is not active"
|
||||
}
|
||||
|
||||
test_license_must_be_offered_when_a_public_project_has_github() {
|
||||
test_license_must_be_offered_when_github_is_chosen() {
|
||||
setup_hosts
|
||||
prepend_route 'GET|/api/v1/licenses|200|[{"key":"MIT","name":"MIT"}]'
|
||||
run_apply "$ANSWERS_GITHUB_PUBLIC"
|
||||
run_apply "$ANSWERS_GITHUB"
|
||||
assert_status "no AGPL" 1 "$STATUS"
|
||||
assert_contains "message" "$ERR" "does not offer the AGPL-3.0 license"
|
||||
assert_not_contains "nothing created" "$(calls)" "POST"
|
||||
# Without GitHub, or for a private project, no license is needed, so the same server is fine.
|
||||
setup_hosts
|
||||
prepend_route 'GET|/api/v1/licenses|200|[{"key":"MIT","name":"MIT"}]'
|
||||
run_dry "$ANSWERS_GITHUB"
|
||||
assert_status "private with GitHub" 0 "$STATUS"
|
||||
# Without GitHub no license is needed, so the same server is fine.
|
||||
setup_hosts
|
||||
prepend_route 'GET|/api/v1/licenses|200|[{"key":"MIT","name":"MIT"}]'
|
||||
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$WORK/.env"
|
||||
@@ -219,7 +215,8 @@ test_apply_sends_the_right_request_bodies() {
|
||||
bodies="$(cat "$WORK/curl.bodies")"
|
||||
assert_contains "GitHub name" "$bodies" '"name":"my-app"'
|
||||
assert_contains "GitHub is created empty" "$bodies" '"private":true,"auto_init":false}'
|
||||
assert_not_contains "a private project gets no license" "$bodies" '"license"'
|
||||
assert_contains "Gitea gets the license" "$bodies" '"license":"AGPL-3.0"'
|
||||
assert_contains "Gitea is initialised with it" "$bodies" '"auto_init":true'
|
||||
assert_contains "default branch" "$bodies" '"default_branch":"main"'
|
||||
assert_contains "description" "$bodies" '"description":"A test app"'
|
||||
assert_contains "mirror target without credentials" "$bodies" '"remote_address":"https://github.com/acme-org/my-app.git"'
|
||||
@@ -242,17 +239,6 @@ test_apply_never_prints_or_passes_a_token() {
|
||||
assert_contains "GitHub token in the private config" "$(cat "$WORK/curl.config")" "Authorization: Bearer $FAKE_GITHUB_PAT"
|
||||
}
|
||||
|
||||
test_apply_sends_the_license_for_a_public_project_with_github() {
|
||||
setup_hosts
|
||||
run_apply "$ANSWERS_GITHUB_PUBLIC"$'y\n'
|
||||
local bodies
|
||||
bodies="$(cat "$WORK/curl.bodies")"
|
||||
assert_contains "Gitea gets the license" "$bodies" '"license":"AGPL-3.0"'
|
||||
assert_contains "Gitea is initialised with it" "$bodies" '"auto_init":true'
|
||||
assert_contains "public" "$bodies" '"private":false'
|
||||
assert_contains "plan names the rule" "$OUT" "with the AGPL-3.0 license (default: GitHub and a public project)"
|
||||
}
|
||||
|
||||
test_apply_with_gitea_only() {
|
||||
setup_hosts
|
||||
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$WORK/.env"
|
||||
@@ -351,7 +337,7 @@ test_gitea_repository_with_only_the_license_can_be_reused() {
|
||||
setup_hosts
|
||||
prepend_route 'GET|/api/v1/repos/TirSystem/my-app|200|{"empty":false}'
|
||||
prepend_route 'GET|/api/v1/repos/TirSystem/my-app/contents|200|[{"name":"LICENSE","type":"file","path":"LICENSE"}]'
|
||||
run_apply "$ANSWERS_GITHUB_PUBLIC"$'y\ny\n'
|
||||
run_apply "$ANSWERS_GITHUB"$'y\ny\n'
|
||||
assert_status "license only" 0 "$STATUS"
|
||||
assert_not_contains "Gitea repository not created again" "$(calls)" "$GITEA_REPO_CALL"
|
||||
assert_eq "mirror created once" "1" "$(calls | grep -c -x -F "$MIRROR_CALL")"
|
||||
@@ -369,7 +355,7 @@ test_gitea_repository_with_only_the_license_can_be_reused() {
|
||||
test_reusing_an_empty_gitea_repository_warns_about_the_license() {
|
||||
setup_hosts
|
||||
prepend_route 'GET|/api/v1/repos/TirSystem/my-app|200|{"empty":true}'
|
||||
run_apply "$ANSWERS_GITHUB_PUBLIC"$'y\ny\n'
|
||||
run_apply "$ANSWERS_GITHUB"$'y\ny\n'
|
||||
assert_status "reuse empty Gitea repository" 0 "$STATUS"
|
||||
assert_contains "warning" "$ERR" "the AGPL-3.0 license is not added to it"
|
||||
}
|
||||
@@ -499,7 +485,7 @@ test_a_repository_this_script_created_earlier_can_be_reused() {
|
||||
setup_hosts
|
||||
prepend_route 'GET|/api/v1/repos/TirSystem/my-app|200|{"empty":false}'
|
||||
prepend_route 'GET|/api/v1/repos/TirSystem/my-app/contents|200|[{"name":"README.md","type":"file"},{"name":"LICENSE","type":"file"}]'
|
||||
run_apply "$ANSWERS_GITHUB_PUBLIC"$'y\ny\n'
|
||||
run_apply "$ANSWERS_GITHUB"$'y\ny\n'
|
||||
assert_status "LICENSE and README.md" 0 "$STATUS"
|
||||
assert_not_contains "not created again" "$(calls)" "$GITEA_REPO_CALL"
|
||||
assert_contains "reported" "$OUT" "Gitea repository : reused"
|
||||
|
||||
@@ -1,219 +0,0 @@
|
||||
#!/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"
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# test-license.sh - tests for the project license (MIL-006): PROJECT_LICENSE
|
||||
# in config.env, the default (AGPL-3.0 only for a public project with GitHub),
|
||||
# "none", invalid values and a license the server does not offer. Sourced by
|
||||
# run-tests.sh.
|
||||
|
||||
# shellcheck disable=SC2016 # snippet and fixture text is literal on purpose
|
||||
|
||||
readonly ANSWERS_GITEA_ONLY_PUBLIC=$'my-app\n\npublic\nTirSystem\nn\n\nn\n'
|
||||
readonly MIT_ROUTE='GET|/api/v1/licenses|200|[{"key":"MIT","name":"MIT"},{"key":"AGPL-3.0","name":"AGPL-3.0"}]'
|
||||
|
||||
# use_license LINE: add a line to config.env and make the server offer MIT.
|
||||
use_license() {
|
||||
printf '%s\n' "$1" >>"$WORK/config.env"
|
||||
prepend_route "$MIT_ROUTE"
|
||||
}
|
||||
|
||||
# gitea_only_env: a Gitea-only run needs no GitHub credentials.
|
||||
gitea_only_env() {
|
||||
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$WORK/.env"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------- key is set
|
||||
|
||||
test_a_license_in_config_is_used_with_github_for_a_private_project() {
|
||||
setup_hosts
|
||||
use_license "PROJECT_LICENSE=MIT"
|
||||
run_apply "$ANSWERS_GITHUB"$'y\n'
|
||||
assert_status "apply" 0 "$STATUS"
|
||||
assert_contains "Gitea gets MIT" "$(cat "$WORK/curl.bodies")" '"license":"MIT"'
|
||||
assert_not_contains "no AGPL" "$(cat "$WORK/curl.bodies")" "AGPL"
|
||||
assert_contains "plan" "$OUT" "create (private) with the MIT license (from config.env)"
|
||||
assert_contains "summary" "$OUT" "License : MIT (from config.env)"
|
||||
}
|
||||
|
||||
test_a_license_in_config_is_used_without_github() {
|
||||
setup_hosts
|
||||
gitea_only_env
|
||||
use_license "PROJECT_LICENSE=MIT"
|
||||
run_apply "$ANSWERS_GITEA_ONLY"$'y\n'
|
||||
assert_status "apply" 0 "$STATUS"
|
||||
assert_contains "Gitea gets MIT" "$(cat "$WORK/curl.bodies")" '"license":"MIT"'
|
||||
assert_contains "initialised with it" "$(cat "$WORK/curl.bodies")" '"auto_init":true'
|
||||
assert_not_contains "no GitHub call" "$(calls)" "api.github.com"
|
||||
assert_contains "the server is asked" "$(calls)" "/licenses"
|
||||
}
|
||||
|
||||
test_the_license_key_is_taken_in_any_case_for_none() {
|
||||
setup_hosts
|
||||
use_license "PROJECT_LICENSE=NONE"
|
||||
run_apply "$ANSWERS_GITHUB_PUBLIC"$'y\n'
|
||||
assert_status "apply" 0 "$STATUS"
|
||||
assert_not_contains "no license" "$(cat "$WORK/curl.bodies")" '"license"'
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------- none
|
||||
|
||||
test_none_gives_no_license_even_for_a_public_project_with_github() {
|
||||
setup_hosts
|
||||
use_license "PROJECT_LICENSE=none"
|
||||
run_apply "$ANSWERS_GITHUB_PUBLIC"$'y\n'
|
||||
assert_status "apply" 0 "$STATUS"
|
||||
assert_not_contains "no license" "$(cat "$WORK/curl.bodies")" '"license"'
|
||||
assert_not_contains "no license lookup" "$(calls)" "/licenses"
|
||||
assert_contains "plan" "$OUT" "create (public), empty"
|
||||
assert_contains "summary" "$OUT" "License : none (from config.env)"
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------- absent
|
||||
|
||||
test_without_the_key_a_public_project_with_github_gets_agpl() {
|
||||
setup_hosts
|
||||
run_dry "$ANSWERS_GITHUB_PUBLIC"
|
||||
assert_status "dry run" 0 "$STATUS"
|
||||
assert_contains "plan" "$OUT" "with the AGPL-3.0 license (default: GitHub and a public project)"
|
||||
assert_contains "summary" "$OUT" "License : AGPL-3.0 (default: GitHub and a public project)"
|
||||
}
|
||||
|
||||
test_without_the_key_a_private_project_with_github_gets_no_license() {
|
||||
setup_hosts
|
||||
run_dry "$ANSWERS_GITHUB"
|
||||
assert_status "dry run" 0 "$STATUS"
|
||||
assert_contains "summary" "$OUT" "License : none (no default for a private project)"
|
||||
assert_not_contains "no license lookup" "$(calls)" "/licenses"
|
||||
}
|
||||
|
||||
test_without_the_key_a_project_without_github_gets_no_license() {
|
||||
setup_hosts
|
||||
gitea_only_env
|
||||
run_dry "$ANSWERS_GITEA_ONLY_PUBLIC"
|
||||
assert_status "dry run" 0 "$STATUS"
|
||||
assert_contains "summary" "$OUT" "License : none"
|
||||
assert_not_contains "no marker" "$OUT" "License : none ("
|
||||
assert_not_contains "no license lookup" "$(calls)" "/licenses"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------- empty and invalid
|
||||
|
||||
test_an_empty_license_stops_before_any_request() {
|
||||
setup_hosts
|
||||
printf 'PROJECT_LICENSE=\n' >>"$WORK/config.env"
|
||||
run_dry "$ANSWERS_GITHUB"
|
||||
assert_status "empty" 1 "$STATUS"
|
||||
assert_contains "key named" "$ERR" "PROJECT_LICENSE in"
|
||||
assert_contains "says empty" "$ERR" "is empty"
|
||||
assert_eq "no request to any host" "" "$(calls)"
|
||||
}
|
||||
|
||||
test_an_invalid_license_stops_before_any_request_and_is_never_asked() {
|
||||
local value
|
||||
for value in 'bad license' 'MIT/2' 'MIT;rm' "$(printf 'a%.0s' {1..65})"; do
|
||||
remove_workdir
|
||||
new_workdir
|
||||
setup_hosts
|
||||
printf 'PROJECT_LICENSE=%s\n' "$value" >>"$WORK/config.env"
|
||||
run_dry "$ANSWERS_GITHUB"
|
||||
assert_status "invalid '$value'" 1 "$STATUS"
|
||||
assert_contains "key named" "$ERR" "PROJECT_LICENSE in"
|
||||
assert_eq "no request to any host" "" "$(calls)"
|
||||
assert_not_contains "never asked" "$ERR" "License ("
|
||||
done
|
||||
}
|
||||
|
||||
# ------------------------------------------------------ not offered
|
||||
|
||||
test_a_license_the_server_does_not_offer_stops_before_anything_is_created() {
|
||||
setup_hosts
|
||||
use_license "PROJECT_LICENSE=Zlib"
|
||||
run_apply "$ANSWERS_GITHUB"$'y\n'
|
||||
assert_status "not offered" 1 "$STATUS"
|
||||
assert_contains "license named" "$ERR" "does not offer the Zlib license"
|
||||
assert_not_contains "nothing created" "$(calls)" "POST"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------- never asked
|
||||
|
||||
test_the_license_is_never_asked() {
|
||||
setup_hosts
|
||||
run_dry "$ANSWERS_GITHUB_PUBLIC"
|
||||
assert_not_contains "no license prompt" "$ERR" "icense ("
|
||||
assert_not_contains "no license prompt, any case" "$ERR" "License:"
|
||||
remove_workdir
|
||||
new_workdir
|
||||
setup_hosts
|
||||
use_license "PROJECT_LICENSE=MIT"
|
||||
run_dry "$ANSWERS_GITHUB"
|
||||
assert_not_contains "no license prompt with the key" "$ERR" "icense ("
|
||||
}
|
||||
|
||||
# ------------------------------------------- mirror and local history
|
||||
|
||||
test_the_license_file_reaches_the_local_project_without_github() {
|
||||
setup_hosts
|
||||
gitea_only_env
|
||||
use_license "PROJECT_LICENSE=MIT"
|
||||
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_eq "the Gitea license commit is the whole history" "1" "$(GIT_CONFIG_GLOBAL="$WORK/gitconfig" GIT_CONFIG_NOSYSTEM=1 git -C "$dir" rev-list --count HEAD)"
|
||||
assert_file_exists "LICENSE from Gitea" "$dir/LICENSE"
|
||||
}
|
||||
+1
-1
@@ -12,7 +12,7 @@
|
||||
# LOCAL_ANSWERS (kept in a variable because $(...) would drop the last newline).
|
||||
local_answers() {
|
||||
if [[ $2 == y ]]; then
|
||||
printf -v LOCAL_ANSWERS 'my-app\nA test app\npublic\nTirSystem\ny\nacme-org\n%s\n%s\n' "$1" "$3"
|
||||
printf -v LOCAL_ANSWERS 'my-app\nA test app\n\nTirSystem\ny\nacme-org\n%s\n%s\n' "$1" "$3"
|
||||
else
|
||||
printf -v LOCAL_ANSWERS 'my-app\n\n\nTirSystem\nn\n%s\n%s\n' "$1" "$3"
|
||||
fi
|
||||
|
||||
@@ -205,7 +205,7 @@ test_summary_marks_the_values_from_config_env() {
|
||||
assert_contains "name" "$OUT" "Repository : my-app (from config.env) (private (from config.env))"
|
||||
assert_contains "description" "$OUT" "Description : A test app (from config.env)"
|
||||
assert_contains "Gitea" "$OUT" "/TirSystem/my-app (from config.env)"
|
||||
assert_contains "GitHub" "$OUT" "/acme-org/my-app (from config.env)"
|
||||
assert_contains "GitHub" "$OUT" "(AGPL license applied) (from config.env)"
|
||||
assert_contains "directory" "$OUT" "Directory : ./my-app (from config.env)"
|
||||
assert_contains "plan gate" "$OUT" "Plan gate : no (from config.env)"
|
||||
}
|
||||
|
||||
+9
-11
@@ -19,10 +19,10 @@ test_full_run_with_github() {
|
||||
after="$(listing)"
|
||||
assert_status "full run" 0 "$STATUS"
|
||||
assert_contains "dry run" "$OUT" "Dry run: nothing was created"
|
||||
assert_contains "plan" "$OUT" "create (private), empty"
|
||||
assert_contains "plan" "$OUT" "create (private) with the AGPL-3.0 license"
|
||||
assert_contains "Gitea link derived from config" "$OUT" "https://git.example.test/TirSystem/my-app"
|
||||
assert_contains "GitHub link uses the chosen organization" "$OUT" "https://github.com/acme-org/my-app"
|
||||
assert_contains "no default license for a private project" "$OUT" "License : none (no default for a private project)"
|
||||
assert_contains "AGPL noted" "$OUT" "AGPL license applied"
|
||||
assert_contains "credential state" "$OUT" "GITEA_TOKEN set, GITHUB_PAT set"
|
||||
assert_not_contains "no Gitea token in output" "$OUT$ERR" "$FAKE_GITEA_TOKEN"
|
||||
assert_not_contains "no GitHub token in output" "$OUT$ERR" "$FAKE_GITHUB_PAT"
|
||||
@@ -47,12 +47,12 @@ test_full_run_without_github() {
|
||||
assert_contains "plan says GitHub is not used" "$OUT" "GitHub repository : not used"
|
||||
}
|
||||
|
||||
test_github_chosen_without_credentials_fails() {
|
||||
test_github_chosen_without_credentials_stops_when_input_ends() {
|
||||
write_fixtures
|
||||
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$WORK/.env"
|
||||
run_cli "$ANSWERS_GITHUB" --config "$WORK/config.env" --env "$WORK/.env"
|
||||
assert_status "missing GitHub credentials" 1 "$STATUS"
|
||||
assert_contains "names the key" "$ERR" "GITHUB_PAT is missing"
|
||||
assert_status "missing GitHub credentials, no answer" 1 "$STATUS"
|
||||
assert_contains "names the credential" "$ERR" "no input available for 'GitHub personal access token'"
|
||||
assert_not_contains "no token in the error" "$OUT$ERR" "$FAKE_GITEA_TOKEN"
|
||||
assert_eq "temporary files removed" "" "$(find "$WORK/tmp" -mindepth 1)"
|
||||
}
|
||||
@@ -201,15 +201,13 @@ test_default_files_are_in_the_project_root() {
|
||||
cp "$WORK/config.env" "$WORK/project/config.env"
|
||||
cp "$WORK/.env" "$WORK/project/.env"
|
||||
STATUS=0
|
||||
(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=$?
|
||||
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 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"
|
||||
# Run from another directory: the defaults follow the script, not the cwd.
|
||||
STATUS=0
|
||||
(cd "$WORK/elsewhere" && PATH="$WORK/bin:$PATH" STUB_DIR="$WORK" TMPDIR="$WORK/tmp" "$BASH" "$WORK/project/src/create-project.sh" \
|
||||
(cd "$WORK" && 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"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user