From be759e38326eac2b11e65a2b582b2431186e338b Mon Sep 17 00:00:00 2001 From: Jens Tirsvad Nielsen Date: Wed, 7 Oct 2026 12:38:01 +0800 Subject: [PATCH 1/8] Give the test tasks of MIL-006 and MIL-007 their own titles The sync matches issues by title, so Test every case moved issue #39 of MIL-005. --- docs/milestones/mil-006-project-license.md | 5 ++--- docs/milestones/mil-007-framework-checklists.md | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/milestones/mil-006-project-license.md b/docs/milestones/mil-006-project-license.md index 795d795..eefa4e3 100644 --- a/docs/milestones/mil-006-project-license.md +++ b/docs/milestones/mil-006-project-license.md @@ -9,8 +9,8 @@ ## Version History | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | -| 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] | +| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Task 4 renamed so its issue title is unique (the sync matches issues by title) | pending | --- @@ -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 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 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 | | --- @@ -84,5 +84,4 @@ 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 diff --git a/docs/milestones/mil-007-framework-checklists.md b/docs/milestones/mil-007-framework-checklists.md index 4f55fd4..2156e80 100644 --- a/docs/milestones/mil-007-framework-checklists.md +++ b/docs/milestones/mil-007-framework-checklists.md @@ -9,8 +9,8 @@ ## Version History | 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 | Added UC-002 and US-002 (global command); tasks 2 and 3 trace to UC-002 | [1cd27f7] | +| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Task 4 renamed so its issue title is unique (the sync matches issues by title) | pending | --- @@ -70,7 +70,7 @@ Decide whether a new project holds the whole framework, including the `qc` check | 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, 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 | | +| 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. | No | | --- -- 2.54.0 From 88b4e5ce6cc10602d15dfc8f35765fbcca2bf7d5 Mon Sep 17 00:00:00 2001 From: Jens Tirsvad Nielsen Date: Wed, 7 Oct 2026 12:38:02 +0800 Subject: [PATCH 2/8] Resolve pending commit links for the task renames --- docs/milestones/mil-006-project-license.md | 3 ++- docs/milestones/mil-007-framework-checklists.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/milestones/mil-006-project-license.md b/docs/milestones/mil-006-project-license.md index eefa4e3..629b095 100644 --- a/docs/milestones/mil-006-project-license.md +++ b/docs/milestones/mil-006-project-license.md @@ -10,7 +10,7 @@ | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | | 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] | -| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Task 4 renamed so its issue title is unique (the sync matches issues by title) | pending | +| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Task 4 renamed so its issue title is unique (the sync matches issues by title) | [be759e3] | --- @@ -85,3 +85,4 @@ The license that applies is resolved in this order: `PROJECT_LICENSE` when set ( [DCD-001]: ../uc-001/dcd.md [MIL-004]: ./mil-004-configurable-details.md [1cd27f7]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/1cd27f77ed844773a969210a11de0d8bb98ac98f +[be759e3]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/be759e38326eac2b11e65a2b582b2431186e338b diff --git a/docs/milestones/mil-007-framework-checklists.md b/docs/milestones/mil-007-framework-checklists.md index 2156e80..ca6fb5a 100644 --- a/docs/milestones/mil-007-framework-checklists.md +++ b/docs/milestones/mil-007-framework-checklists.md @@ -10,7 +10,7 @@ | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | | 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] | -| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Task 4 renamed so its issue title is unique (the sync matches issues by title) | pending | +| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Task 4 renamed so its issue title is unique (the sync matches issues by title) | [be759e3] | --- @@ -80,3 +80,4 @@ Decide whether a new project holds the whole framework, including the `qc` check [UC-002]: ../uc-002/uc.md [MIL-003]: ./mil-003-scaffold-and-release.md [1cd27f7]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/1cd27f77ed844773a969210a11de0d8bb98ac98f +[be759e3]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/be759e38326eac2b11e65a2b582b2431186e338b -- 2.54.0 From 0ab50068bf9e5be82a801af9dbe5b763eeaf7f31 Mon Sep 17 00:00:00 2001 From: Jens Tirsvad Nielsen Date: Wed, 7 Oct 2026 12:42:05 +0800 Subject: [PATCH 3/8] Plan the default configuration files: working folder first, then the checkout - --config and --env, else ./config.env and ./.env, else the checkout's - Files named before any request; a file from the working folder needs a yes - UC-002, US-002, MIL-007 criteria 9 and 10, models and contracts updated - Review record RC-029 --- docs/artifact-registry.md | 2 +- docs/business-case.md | 8 +- docs/dcd.md | 22 +++-- docs/dictionary.md | 8 +- docs/domain-model.md | 6 +- .../mil-007-framework-checklists.md | 13 +-- .../reviews/rc-029-default-config-files.md | 82 +++++++++++++++++++ docs/sqa/traceability-matrix.md | 25 +++--- docs/uc-002/dcd.md | 25 ++++-- docs/uc-002/dm.md | 5 +- docs/uc-002/oc.md | 15 ++-- docs/uc-002/sd.md | 32 +++++--- docs/uc-002/ssd.md | 4 +- docs/uc-002/uc.md | 14 ++-- docs/user-stories.md | 9 +- 15 files changed, 201 insertions(+), 69 deletions(-) create mode 100644 docs/sqa/reviews/rc-029-default-config-files.md diff --git a/docs/artifact-registry.md b/docs/artifact-registry.md index 5a92c3a..6e96f87 100644 --- a/docs/artifact-registry.md +++ b/docs/artifact-registry.md @@ -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 | 029 | +| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 030 | | TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 | ## Languages diff --git a/docs/business-case.md b/docs/business-case.md index f0b1237..edd0144 100644 --- a/docs/business-case.md +++ b/docs/business-case.md @@ -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 | pending | --- @@ -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`. +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. ## 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, and the new project lands in the folder it was started in. +- 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). - 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 the checkout's `config.env` and `.env` and creates the project under that folder | Test run through a link; the README example run once | +| 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 | ## Risks diff --git a/docs/dcd.md b/docs/dcd.md index ec085d1..75abd01 100644 --- a/docs/dcd.md +++ b/docs/dcd.md @@ -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 | pending | --- @@ -33,7 +33,7 @@ enum Visibility { } class ProjectCreator <> { - +startProjectCreation(checkout : Checkout, workingFolder : WorkingFolder, configPath : Path [0..1], envPath : Path [0..1]) : PromptSet + +startProjectCreation(workingFolder : WorkingFolder, configFiles : ConfigFiles) : PromptSet +provideProjectDetails(name : String, description : String, visibility : Visibility, giteaOwner : Owner, githubOwner : Owner [0..1], directory : Path, enablePlanGate : Boolean, writeEnvFile : Boolean) : Summary } class ConfigLoader { @@ -83,6 +83,7 @@ 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 { @@ -92,6 +93,12 @@ class Checkout { } class WorkingFolder { -path : Path + +configFile() : Path [0..1] + +envFile() : Path [0..1] +} +class ConfigFiles { + -configFile : Path + -envFile : Path } class Run { -isApply : Boolean @@ -186,6 +193,8 @@ 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 @@ -249,9 +258,10 @@ 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, and starts the run. | none | `resolveCheckout`, `currentFolder`, `startFromWorkingFolder` | +| `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` | | `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. | `path` | none | +| `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 | | `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` | @@ -291,9 +301,10 @@ Repository "0..*" --> "1" Visibility | Method signature | Operation Contract / SD message | | --- | --- | -| `ProjectCreator.startProjectCreation(checkout, workingFolder, configPath, envPath) : PromptSet` | [OC-001] `startProjectCreation`; [SD-001] `startProjectCreation()`; [SD-002] `startProjectCreation(checkout, workingFolder, ...)` | +| `ProjectCreator.startProjectCreation(workingFolder, configFiles) : 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` | @@ -362,5 +373,4 @@ 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 diff --git a/docs/dictionary.md b/docs/dictionary.md index d54e024..6381675 100644 --- a/docs/dictionary.md +++ b/docs/dictionary.md @@ -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 | pending | --- @@ -49,8 +49,9 @@ 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` and `PromptSet` appear in [OC-001] but - have no PO term: they are system concepts, not domain concepts, and are not +- `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 in the Domain Model. - `InstallResult` and the enumeration `Visibility` appear only in [DCD-001]: `InstallResult` carries the three results of one operation, and `Visibility` @@ -67,5 +68,4 @@ 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 diff --git a/docs/domain-model.md b/docs/domain-model.md index 73c57d7..38d7802 100644 --- a/docs/domain-model.md +++ b/docs/domain-model.md @@ -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 | pending | --- @@ -128,6 +128,7 @@ 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 ``` @@ -155,7 +156,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 and under which the new project is created by default | path | [UC-002] step 2 "working folder" | +| 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" | | 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 @@ -189,6 +190,7 @@ 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 diff --git a/docs/milestones/mil-007-framework-checklists.md b/docs/milestones/mil-007-framework-checklists.md index ca6fb5a..cbe054c 100644 --- a/docs/milestones/mil-007-framework-checklists.md +++ b/docs/milestones/mil-007-framework-checklists.md @@ -9,8 +9,8 @@ ## Version History | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | -| 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] | | 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Task 4 renamed so its issue title is unique (the sync matches issues by title) | [be759e3] | +| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | The configuration files default to the working folder's, then the checkout's, confirmed and named (deliverable 3, criteria 9 and 10, tasks 2 to 4) | pending | --- @@ -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 `./` 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. +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 `./` 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. ## Go / No-Go Criteria @@ -36,6 +36,8 @@ 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 @@ -68,9 +70,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, 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 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. | 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, 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 | | --- @@ -79,5 +81,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 -[1cd27f7]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/1cd27f77ed844773a969210a11de0d8bb98ac98f [be759e3]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/be759e38326eac2b11e65a2b582b2431186e338b diff --git a/docs/sqa/reviews/rc-029-default-config-files.md b/docs/sqa/reviews/rc-029-default-config-files.md new file mode 100644 index 0000000..7ad067b --- /dev/null +++ b/docs/sqa/reviews/rc-029-default-config-files.md @@ -0,0 +1,82 @@ +# 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 | pending | + +--- + +## 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 diff --git a/docs/sqa/traceability-matrix.md b/docs/sqa/traceability-matrix.md index 5495783..5305856 100644 --- a/docs/sqa/traceability-matrix.md +++ b/docs/sqa/traceability-matrix.md @@ -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] | +| [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] | | [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] | +| [MIL-007] | MIL | [BC-001], [PP-001] | [US-001], [UC-002] | [RC-022], [RC-029] | | [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] | +| [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] | | [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] | -| [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] | +| [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] | | [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] | -| [DICT-001] | DICT | [BC-001], [SA-001], [DM-001], [DM-002] | [OC-001], [SD-001] | [RC-008], [RC-020], [RC-025] | +| [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] | | [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] | +| [DCD-002] | DCD | [DCD-001], [DCD-003], [DM-002], [DICT-001] | - | [RC-021], [RC-028], [RC-029] | ## Coverage Notes @@ -80,6 +80,7 @@ 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 diff --git a/docs/uc-002/dcd.md b/docs/uc-002/dcd.md index f24dc71..476d78f 100644 --- a/docs/uc-002/dcd.md +++ b/docs/uc-002/dcd.md @@ -10,6 +10,7 @@ | 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 | pending | --- @@ -24,6 +25,7 @@ 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 { @@ -33,14 +35,22 @@ class Checkout { } class WorkingFolder { -path : Path + +configFile() : Path [0..1] + +envFile() : Path [0..1] +} +class ConfigFiles { + -configFile : Path + -envFile : Path } class ProjectCreator { - +startProjectCreation(checkout : Checkout, workingFolder : WorkingFolder, configPath : Path [0..1], envPath : Path [0..1]) : PromptSet + +startProjectCreation(workingFolder : WorkingFolder, configFiles : ConfigFiles) : 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 @@ -50,9 +60,10 @@ 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, and starts the run. | none | `resolveCheckout`, `currentFolder`, `startFromWorkingFolder` | +| `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` | | `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. | `path` | none | +| `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 | The concept Command Link has no class: it is a link the Maintainer makes with the shell, and the system only follows it. @@ -63,8 +74,10 @@ 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 | -| `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` | +| `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` | ## Pattern Annotations @@ -75,7 +88,7 @@ The concept Command Link has no class: it is a link the Maintainer makes with th ## Dependency Check -`Launcher` depends on `ProjectCreator`, `Checkout` and `WorkingFolder`; none of them depends on `Launcher`, so no cycle is added. +`Launcher` depends on `ProjectCreator`, `Checkout`, `WorkingFolder` and `ConfigFiles`; none of them depends on `Launcher`, so no cycle is added. --- diff --git a/docs/uc-002/dm.md b/docs/uc-002/dm.md index 4d0d5f3..2a603e3 100644 --- a/docs/uc-002/dm.md +++ b/docs/uc-002/dm.md @@ -10,6 +10,7 @@ | 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 | pending | --- @@ -46,6 +47,7 @@ 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 ``` @@ -56,7 +58,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 and under which the new project is created by default | path | [UC-002] step 2 "working folder" | +| 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" | ## Association Table @@ -66,6 +68,7 @@ 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 diff --git a/docs/uc-002/oc.md b/docs/uc-002/oc.md index c6c46c6..87e6793 100644 --- a/docs/uc-002/oc.md +++ b/docs/uc-002/oc.md @@ -10,10 +10,11 @@ | 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 | pending | --- -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. +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. ## Contract: startFromWorkingFolder @@ -21,7 +22,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, Configuration | +| Concepts | Run, CommandLink, Checkout, WorkingFolder, ConfigFiles, Configuration | **Preconditions** @@ -34,16 +35,18 @@ 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 `./` under the `WorkingFolder`, never under the `Checkout`. -- 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. +- 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. **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 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 | +| `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 | --- diff --git a/docs/uc-002/sd.md b/docs/uc-002/sd.md index d481059..89501b0 100644 --- a/docs/uc-002/sd.md +++ b/docs/uc-002/sd.md @@ -10,6 +10,7 @@ | 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 | pending | --- @@ -28,6 +29,7 @@ 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 @@ -37,13 +39,20 @@ L -> CK : Checkout(path) L -> L : currentFolder() create WF L -> WF : WorkingFolder(path) -alt the Checkout's own files are not found +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 L --> Maintainer : error naming the folder looked in end -L -> PC : startProjectCreation(checkout, workingFolder, configPath, envPath) +L -> PC : startProjectCreation(workingFolder, configFiles) activate PC -alt config.env or .env not found, or a value malformed - PC --> L : error naming the path or the key +alt a value in config.env or .env is malformed + PC --> L : error naming the key end PC --> L : promptSet deactivate PC @@ -56,9 +65,9 @@ deactivate L | Pattern (GRASP / GoF) | Applied to | Rationale | | --- | --- | --- | -| Information Expert | `Launcher.resolveCheckout` | The launcher knows how the script was invoked, so it is the one that can follow the command link | +| 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 | | Controller | `Launcher` | One object takes the system operation and hands the work to `ProjectCreator`; `ProjectCreator` stays unaware of links | -| Low Coupling | `ProjectCreator` receives `checkout` and `workingFolder` as values | The use case [UC-001] runs unchanged whatever way the script was started | +| Low Coupling | `ProjectCreator` receives `workingFolder` and `configFiles` as values | The use case [UC-001] runs unchanged whatever way the script was started | ### Postcondition Coverage @@ -67,14 +76,15 @@ 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(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 | +| 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 | ### Responsibility Check -`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]. +`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]. --- diff --git a/docs/uc-002/ssd.md b/docs/uc-002/ssd.md index 8808a43..defc644 100644 --- a/docs/uc-002/ssd.md +++ b/docs/uc-002/ssd.md @@ -10,6 +10,7 @@ | 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 | pending | --- @@ -37,7 +38,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. `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. 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. ## Lifecycle Notes @@ -48,4 +49,5 @@ 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 diff --git a/docs/uc-002/uc.md b/docs/uc-002/uc.md index 9a41f26..a843d7e 100644 --- a/docs/uc-002/uc.md +++ b/docs/uc-002/uc.md @@ -10,6 +10,7 @@ | 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) | pending | --- @@ -25,7 +26,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 and holds `config.env` and `.env` as described in the README, or the Maintainer points to them with `--config` and `--env`. + - 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`. - 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. @@ -37,7 +38,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 reads `config.env` and `.env` from the checkout, or from the files named by `--config` and `--env`. +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. 5. The system runs [UC-001] (`<>`) 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. @@ -49,8 +50,10 @@ 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 found: - 1. The system stops before any change, names the path it looked in and says that `--config` and `--env` can point elsewhere. +- 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. ### Special Requirements / Business Rules @@ -58,7 +61,8 @@ | --- | --- | | 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 read from the checkout, never from the working folder, unless `--config` and `--env` name them | +| 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 | | 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 | diff --git a/docs/user-stories.md b/docs/user-stories.md index f98e287..9f08fa6 100644 --- a/docs/user-stories.md +++ b/docs/user-stories.md @@ -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 | pending | --- @@ -136,10 +136,11 @@ 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, finds its own files and reads `config.env` and `.env` from the checkout. +- 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 the Maintainer stands in a folder, when the project directory is not preset, then its default is `./` under that folder, never under the checkout. -- 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 `--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 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 | -- 2.54.0 From b324acbfae77578ce55a3b34bb36162c23ec586e Mon Sep 17 00:00:00 2001 From: Jens Tirsvad Nielsen Date: Wed, 7 Oct 2026 12:42:08 +0800 Subject: [PATCH 4/8] Resolve pending commit links for the default configuration files --- docs/business-case.md | 3 ++- docs/dcd.md | 3 ++- docs/dictionary.md | 3 ++- docs/domain-model.md | 3 ++- docs/milestones/mil-007-framework-checklists.md | 3 ++- docs/sqa/reviews/rc-029-default-config-files.md | 3 ++- docs/uc-002/dcd.md | 3 ++- docs/uc-002/dm.md | 3 ++- docs/uc-002/oc.md | 3 ++- docs/uc-002/sd.md | 3 ++- docs/uc-002/ssd.md | 3 ++- docs/uc-002/uc.md | 3 ++- docs/user-stories.md | 3 ++- 13 files changed, 26 insertions(+), 13 deletions(-) diff --git a/docs/business-case.md b/docs/business-case.md index edd0144..a6affc6 100644 --- a/docs/business-case.md +++ b/docs/business-case.md @@ -10,7 +10,7 @@ | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | | 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 | pending | +| 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] | --- @@ -154,3 +154,4 @@ 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 diff --git a/docs/dcd.md b/docs/dcd.md index 75abd01..79397c8 100644 --- a/docs/dcd.md +++ b/docs/dcd.md @@ -10,7 +10,7 @@ | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | | 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 | pending | +| 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] | --- @@ -374,3 +374,4 @@ SOLID check: no class has more than one reason to change (one host API, one kind [MIL-005]: ./milestones/mil-005-credentials.md [DICT-001]: ./dictionary.md [1cd27f7]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/1cd27f77ed844773a969210a11de0d8bb98ac98f +[0ab5006]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/0ab50068bf9e5be82a801af9dbe5b763eeaf7f31 diff --git a/docs/dictionary.md b/docs/dictionary.md index 6381675..54e9ff4 100644 --- a/docs/dictionary.md +++ b/docs/dictionary.md @@ -10,7 +10,7 @@ | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | | 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 | pending | +| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | `ConfigFiles` named as a system concept without a PO term | [0ab5006] | --- @@ -69,3 +69,4 @@ Maps each Product Owner (PO) term to its professional IT term. PO language: Engl [OC-001]: ./uc-001/oc.md [DCD-001]: ./uc-001/dcd.md [1cd27f7]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/1cd27f77ed844773a969210a11de0d8bb98ac98f +[0ab5006]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/0ab50068bf9e5be82a801af9dbe5b763eeaf7f31 diff --git a/docs/domain-model.md b/docs/domain-model.md index 38d7802..53a9db9 100644 --- a/docs/domain-model.md +++ b/docs/domain-model.md @@ -10,7 +10,7 @@ | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | | 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 | pending | +| 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] | --- @@ -208,3 +208,4 @@ 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 diff --git a/docs/milestones/mil-007-framework-checklists.md b/docs/milestones/mil-007-framework-checklists.md index cbe054c..c856655 100644 --- a/docs/milestones/mil-007-framework-checklists.md +++ b/docs/milestones/mil-007-framework-checklists.md @@ -10,7 +10,7 @@ | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | | 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Task 4 renamed so its issue title is unique (the sync matches issues by title) | [be759e3] | -| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | The configuration files default to the working folder's, then the checkout's, confirmed and named (deliverable 3, criteria 9 and 10, tasks 2 to 4) | pending | +| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | The configuration files default to the working folder's, then the checkout's, confirmed and named (deliverable 3, criteria 9 and 10, tasks 2 to 4) | [0ab5006] | --- @@ -82,3 +82,4 @@ Decide whether a new project holds the whole framework, including the `qc` check [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 diff --git a/docs/sqa/reviews/rc-029-default-config-files.md b/docs/sqa/reviews/rc-029-default-config-files.md index 7ad067b..69e1f7d 100644 --- a/docs/sqa/reviews/rc-029-default-config-files.md +++ b/docs/sqa/reviews/rc-029-default-config-files.md @@ -9,7 +9,7 @@ ## Version History | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | -| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Initial version | pending | +| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Initial version | [0ab5006] | --- @@ -80,3 +80,4 @@ Go-with-conditions — the change is consistent across the documents, and the se [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 diff --git a/docs/uc-002/dcd.md b/docs/uc-002/dcd.md index 476d78f..ac04287 100644 --- a/docs/uc-002/dcd.md +++ b/docs/uc-002/dcd.md @@ -10,7 +10,7 @@ | 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 | pending | +| 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] | --- @@ -99,3 +99,4 @@ 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 diff --git a/docs/uc-002/dm.md b/docs/uc-002/dm.md index 2a603e3..2b8dc89 100644 --- a/docs/uc-002/dm.md +++ b/docs/uc-002/dm.md @@ -10,7 +10,7 @@ | 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 | pending | +| 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] | --- @@ -86,3 +86,4 @@ 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 diff --git a/docs/uc-002/oc.md b/docs/uc-002/oc.md index 87e6793..d9e932b 100644 --- a/docs/uc-002/oc.md +++ b/docs/uc-002/oc.md @@ -10,7 +10,7 @@ | 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 | pending | +| 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] | --- @@ -55,3 +55,4 @@ 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 diff --git a/docs/uc-002/sd.md b/docs/uc-002/sd.md index 89501b0..3ded423 100644 --- a/docs/uc-002/sd.md +++ b/docs/uc-002/sd.md @@ -10,7 +10,7 @@ | 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 | pending | +| 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] | --- @@ -93,3 +93,4 @@ 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 diff --git a/docs/uc-002/ssd.md b/docs/uc-002/ssd.md index defc644..8e6ba82 100644 --- a/docs/uc-002/ssd.md +++ b/docs/uc-002/ssd.md @@ -10,7 +10,7 @@ | 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 | pending | +| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Names the confirmation of a configuration file from the working folder as out of scope | [0ab5006] | --- @@ -51,3 +51,4 @@ One script run, as in [UC-001]. The command link persists between runs; the syst [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 diff --git a/docs/uc-002/uc.md b/docs/uc-002/uc.md index a843d7e..eb31767 100644 --- a/docs/uc-002/uc.md +++ b/docs/uc-002/uc.md @@ -10,7 +10,7 @@ | 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) | pending | +| 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] | --- @@ -77,3 +77,4 @@ [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 diff --git a/docs/user-stories.md b/docs/user-stories.md index 9f08fa6..5154217 100644 --- a/docs/user-stories.md +++ b/docs/user-stories.md @@ -10,7 +10,7 @@ | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | | 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 | pending | +| 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] | --- @@ -166,3 +166,4 @@ 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 -- 2.54.0 From e960dd9da84fb75ce8559e2b12fb08c3beb5730a Mon Sep 17 00:00:00 2001 From: Jens Tirsvad Nielsen Date: Wed, 7 Oct 2026 13:08:49 +0800 Subject: [PATCH 5/8] Set the project license in config.env; AGPL-3.0 default only for a public GitHub project - PROJECT_LICENSE (a Gitea license key, or none) is read, checked and never asked - Without it AGPL-3.0 applies only when GitHub is chosen and the project is public - The license is applied on Gitea with or without GitHub, and checked against the server first - Plan and summary name the license and where it came from - MIL-006 accepted; README and config.env.example document the key - Tests: new test-license.sh; existing tests use a public project where they expect AGPL-3.0 Task: MIL-006#1 Task: MIL-006#2 Task: MIL-006#3 Task: MIL-006#4 Refs #44 Refs #45 Refs #46 Refs #50 --- README.md | 25 ++-- config.env.example | 6 + docs/milestones/mil-006-project-license.md | 4 +- src/create-project.sh | 5 +- src/lib/apply.sh | 4 +- src/lib/config.sh | 1 + src/lib/constants.sh | 4 +- src/lib/localproject.sh | 4 +- src/lib/plan.sh | 4 +- src/lib/preflight.sh | 8 +- src/lib/project.sh | 35 ++++- src/lib/repositories.sh | 4 +- src/lib/validate.sh | 5 + tests/lib.sh | 10 ++ tests/test-hosts.sh | 30 +++- tests/test-license.sh | 162 +++++++++++++++++++++ tests/test-local.sh | 2 +- tests/test-presets.sh | 2 +- tests/test-security.sh | 4 +- 19 files changed, 279 insertions(+), 40 deletions(-) create mode 100644 tests/test-license.sh diff --git a/README.md b/README.md index b7e4826..54a48c5 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,7 @@ 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 @@ -111,7 +112,12 @@ 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. -- Only these eight details can be set. The confirmations stay questions that +- `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 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`. @@ -119,7 +125,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 eight set, a run asks only the confirmations: +With all of them set, a run asks only the confirmations: ```bash src/create-project.sh --apply # asks only "Create these now (y/n) [n]" @@ -153,8 +159,8 @@ with read-only requests and prints a plan: ```text Plan: - 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 + 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 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) @@ -167,7 +173,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 AGPL-3.0 license if GitHub was chosen); +2. the Gitea repository (with the license that applies: `PROJECT_LICENSE`, or AGPL-3.0 for a public project with GitHub); 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 @@ -181,10 +187,11 @@ hooks refuse commits on `main`. ### Choices -- **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. +- **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. - **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. diff --git a/config.env.example b/config.env.example index 65a59af..b06391b 100644 --- a/config.env.example +++ b/config.env.example @@ -46,6 +46,12 @@ GITEA_API_URL=https:///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. diff --git a/docs/milestones/mil-006-project-license.md b/docs/milestones/mil-006-project-license.md index 629b095..9c489eb 100644 --- a/docs/milestones/mil-006-project-license.md +++ b/docs/milestones/mil-006-project-license.md @@ -9,8 +9,8 @@ ## Version History | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | -| 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] | -| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Task 4 renamed so its issue title is unique (the sync matches issues by title) | [be759e3] | +| 2026-10-07 | Deprecated | Jens Tirsvad Nielsen | S02 | 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] | --- diff --git a/src/create-project.sh b/src/create-project.sh index f0ad673..1a44148 100644 --- a/src/create-project.sh +++ b/src/create-project.sh @@ -10,8 +10,9 @@ # repositories and the mirror, then the local project: its directory, git # repository, remotes (no credential in any address), the framework as a # submodule, the framework's skills and git hooks (and the plan gate if -# chosen) and its templates. Choosing GitHub also applies the AGPL-3.0 -# license to the Gitea repository. No commit is made in the new project. +# 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. # # Dry run by default # Without --apply the script only reads from GitHub and Gitea (GET diff --git a/src/lib/apply.sh b/src/lib/apply.sh index 124e11e..9b8c2f9 100644 --- a/src/lib/apply.sh +++ b/src/lib/apply.sh @@ -47,9 +47,9 @@ confirm_reuse() { die "stopped: choose another name or remove the existing repository" fi done - if ((${STATE[reuse_gitea]:-0})) && ((PROJECT[has_github])) && + if ((${STATE[reuse_gitea]:-0})) && [[ -n ${PROJECT[license]} ]] && [[ ${STATE[gitea_repo]} == empty ]]; then - warn "the empty Gitea repository is reused as it is: the $AGPL_LICENSE_KEY license is not added to it" + warn "the empty Gitea repository is reused as it is: the ${PROJECT[license]} license is not added to it" fi } diff --git a/src/lib/config.sh b/src/lib/config.sh index 6f691f3..96878c1 100644 --- a/src/lib/config.sh +++ b/src/lib/config.sh @@ -142,6 +142,7 @@ 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() { diff --git a/src/lib/constants.sh b/src/lib/constants.sh index 5e0aa16..959b60d 100644 --- a/src/lib/constants.sh +++ b/src/lib/constants.sh @@ -18,6 +18,7 @@ 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. @@ -25,6 +26,7 @@ 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 PLAN_STEPS=("GitHub repository" "Gitea repository" "Push mirror" "Local project" "Framework" "Skills and hooks" "Templates") @@ -32,7 +34,7 @@ readonly PLAN_STEPS=("GitHub repository" "Gitea repository" "Push mirror" 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) + GITHUB_OWNER PROJECT_DIRECTORY ENABLE_PLAN_GATE PROJECT_LICENSE) readonly CREDENTIAL_KEYS=(GITHUB_PAT GITHUB_USER GITEA_TOKEN) CONFIG_FILE="$PROJECT_ROOT/config.env" diff --git a/src/lib/localproject.sh b/src/lib/localproject.sh index 231809c..cb51dc6 100644 --- a/src/lib/localproject.sh +++ b/src/lib/localproject.sh @@ -72,7 +72,7 @@ checkout_gitea_history() { } # create_local_project: the directory, the git repository on main, the -# remotes and, when GitHub is chosen, the license history. No commit is made. +# remotes and, when a license applies, the license history. No commit is made. create_local_project() { local label="Local project" dir="${PROJECT[directory]}" begin_step "$label" @@ -84,6 +84,8 @@ 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))" diff --git a/src/lib/plan.sh b/src/lib/plan.sh index f735c6b..8c380ef 100644 --- a/src/lib/plan.sh +++ b/src/lib/plan.sh @@ -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 ((PROJECT[has_github])); then - gitea_action="create (${PROJECT[visibility]}) with the $AGPL_LICENSE_KEY license" + elif [[ -n ${PROJECT[license]} ]]; then + gitea_action="create (${PROJECT[visibility]}) with the ${PROJECT[license]} license$(license_note)" else gitea_action="create (${PROJECT[visibility]}), empty" fi diff --git a/src/lib/preflight.sh b/src/lib/preflight.sh index 7aea73f..3d41d3e 100644 --- a/src/lib/preflight.sh +++ b/src/lib/preflight.sh @@ -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 "$AGPL_LICENSE_KEY" || - die "the Gitea server does not offer the $AGPL_LICENSE_KEY license" + json_has_value "$HTTP_BODY_FILE" key "${PROJECT[license]}" || + die "the Gitea server does not offer the ${PROJECT[license]} 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 ((PROJECT[has_github])); then + if [[ -n ${PROJECT[license]} ]]; 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 ]] && ((PROJECT[has_github])); then + if [[ $host == gitea && -n ${PROJECT[license]} ]]; 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" diff --git a/src/lib/project.sh b/src/lib/project.sh index 6fce350..4e6ab8c 100644 --- a/src/lib/project.sh +++ b/src/lib/project.sh @@ -4,7 +4,7 @@ # # Part of create-project.sh: sourced by it, never run on its own. # -# Provides: preset_detail, collect_project_details, collect_github_details, source_note, yes_no, credential_state, print_summary +# Provides: preset_detail, resolve_license, license_note, 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,6 +14,33 @@ 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 || @@ -29,6 +56,7 @@ 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" @@ -46,7 +74,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 (applies the AGPL license)" y + prompt_yes_no "Also create a GitHub repository" y fi PROJECT[has_github]="$REPLY" PROJECT[github_owner]="" @@ -90,10 +118,11 @@ 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]} (AGPL license applied)$(source_note github_owner)" + say " GitHub : ${CONFIG[GITHUB_WEB_URL]}/${PROJECT[github_owner]}/${PROJECT[name]}$(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)," \ diff --git a/src/lib/repositories.sh b/src/lib/repositories.sh index eb9205d..0a7b72a 100644 --- a/src/lib/repositories.sh +++ b/src/lib/repositories.sh @@ -18,8 +18,8 @@ repo_body() { "${PROJECT[name]}" "$description" "$private" return 0 fi - if ((PROJECT[has_github])); then - extra=',"auto_init":true,"license":"'"$AGPL_LICENSE_KEY"'"' + if [[ -n ${PROJECT[license]} ]]; then + extra=',"auto_init":true,"license":"'"${PROJECT[license]}"'"' else extra=',"auto_init":false' fi diff --git a/src/lib/validate.sh b/src/lib/validate.sh index 7902f07..3430024 100644 --- a/src/lib/validate.sh +++ b/src/lib/validate.sh @@ -58,6 +58,11 @@ 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)?$ ]] diff --git a/tests/lib.sh b/tests/lib.sh index 1d6408d..c3e8641 100644 --- a/tests/lib.sh +++ b/tests/lib.sh @@ -21,6 +21,16 @@ 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="" diff --git a/tests/test-hosts.sh b/tests/test-hosts.sh index c6e709f..687c521 100644 --- a/tests/test-hosts.sh +++ b/tests/test-hosts.sh @@ -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) with the AGPL-3.0 license https://git.example.test/TirSystem/my-app" + assert_contains "Gitea plan" "$OUT" "Gitea repository : create (private), empty 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,14 +148,18 @@ 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_github_is_chosen() { +test_license_must_be_offered_when_a_public_project_has_github() { setup_hosts prepend_route 'GET|/api/v1/licenses|200|[{"key":"MIT","name":"MIT"}]' - run_apply "$ANSWERS_GITHUB" + run_apply "$ANSWERS_GITHUB_PUBLIC" 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 no license is needed, so the same server is fine. + # 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" setup_hosts prepend_route 'GET|/api/v1/licenses|200|[{"key":"MIT","name":"MIT"}]' printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$WORK/.env" @@ -215,8 +219,7 @@ 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_contains "Gitea gets the license" "$bodies" '"license":"AGPL-3.0"' - assert_contains "Gitea is initialised with it" "$bodies" '"auto_init":true' + assert_not_contains "a private project gets no license" "$bodies" '"license"' 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"' @@ -239,6 +242,17 @@ 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" @@ -337,7 +351,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"$'y\ny\n' + run_apply "$ANSWERS_GITHUB_PUBLIC"$'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")" @@ -355,7 +369,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"$'y\ny\n' + run_apply "$ANSWERS_GITHUB_PUBLIC"$'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" } diff --git a/tests/test-license.sh b/tests/test-license.sh new file mode 100644 index 0000000..d197216 --- /dev/null +++ b/tests/test-license.sh @@ -0,0 +1,162 @@ +#!/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" +} diff --git a/tests/test-local.sh b/tests/test-local.sh index 8e7e77f..8fe1f5d 100644 --- a/tests/test-local.sh +++ b/tests/test-local.sh @@ -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\n\nTirSystem\ny\nacme-org\n%s\n%s\n' "$1" "$3" + printf -v LOCAL_ANSWERS 'my-app\nA test app\npublic\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 diff --git a/tests/test-presets.sh b/tests/test-presets.sh index 5ed8d84..8bcc930 100644 --- a/tests/test-presets.sh +++ b/tests/test-presets.sh @@ -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" "(AGPL license applied) (from config.env)" + assert_contains "GitHub" "$OUT" "/acme-org/my-app (from config.env)" assert_contains "directory" "$OUT" "Directory : ./my-app (from config.env)" assert_contains "plan gate" "$OUT" "Plan gate : no (from config.env)" } diff --git a/tests/test-security.sh b/tests/test-security.sh index 9ad23a0..950f546 100644 --- a/tests/test-security.sh +++ b/tests/test-security.sh @@ -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) with the AGPL-3.0 license" + assert_contains "plan" "$OUT" "create (private), empty" 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 "AGPL noted" "$OUT" "AGPL license applied" + assert_contains "no default license for a private project" "$OUT" "License : none (no default for a private project)" 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" -- 2.54.0 From 875dfba1b217784011e29cfd00f0ee5341c40778 Mon Sep 17 00:00:00 2001 From: Jens Tirsvad Nielsen Date: Wed, 7 Oct 2026 13:28:47 +0800 Subject: [PATCH 6/8] Use a public project in the test that reuses a repository holding LICENSE and README.md A private project with GitHub no longer gets a license, so only a public one expects it. Task: MIL-006#4 Refs #50 --- tests/test-hosts.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test-hosts.sh b/tests/test-hosts.sh index 687c521..ebe3946 100644 --- a/tests/test-hosts.sh +++ b/tests/test-hosts.sh @@ -499,7 +499,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"$'y\ny\n' + run_apply "$ANSWERS_GITHUB_PUBLIC"$'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" -- 2.54.0 From 1a283430b3451f252c634deeca2ae14a6ce36034 Mon Sep 17 00:00:00 2001 From: Jens Tirsvad Nielsen Date: Wed, 7 Oct 2026 13:40:42 +0800 Subject: [PATCH 7/8] WIP MIL-007: fetch the framework's qc, follow links, default configuration files - git submodule update --init --recursive after adding or reusing the framework - Follow links to the script so SCRIPT_DIR and the checkout are the real ones - config.env and .env: --config/--env, else ./ in the working folder, else the checkout's - Name the files used; a file from the working folder needs a yes before any request - MIL-007 accepted; test fixtures for the qc checklists; tests/test-launch.sh Work in progress: the README section is not written yet, and four tests of test-launch.sh fail and two shellcheck warnings remain; they are fixed next. Task: MIL-007#1 Task: MIL-007#2 Refs #47 Refs #48 Refs #51 --- .../mil-007-framework-checklists.md | 4 +- src/create-project.sh | 40 +++- src/lib/cli.sh | 2 + src/lib/config.sh | 47 ++++ src/lib/constants.sh | 10 +- src/lib/framework.sh | 16 +- tests/lib.sh | 6 +- tests/test-launch.sh | 218 ++++++++++++++++++ 8 files changed, 328 insertions(+), 15 deletions(-) create mode 100644 tests/test-launch.sh diff --git a/docs/milestones/mil-007-framework-checklists.md b/docs/milestones/mil-007-framework-checklists.md index c856655..a65722c 100644 --- a/docs/milestones/mil-007-framework-checklists.md +++ b/docs/milestones/mil-007-framework-checklists.md @@ -9,8 +9,8 @@ ## Version History | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | -| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | Task 4 renamed so its issue title is unique (the sync matches issues by title) | [be759e3] | -| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | The configuration files default to the working folder's, then the checkout's, confirmed and named (deliverable 3, criteria 9 and 10, tasks 2 to 4) | [0ab5006] | +| 2026-10-07 | 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] | --- diff --git a/src/create-project.sh b/src/create-project.sh index 1a44148..784a7fb 100644 --- a/src/create-project.sh +++ b/src/create-project.sh @@ -86,18 +86,39 @@ 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. -readonly SCRIPT_FILE="${BASH_SOURCE[0]}" -case "${BASH_SOURCE[0]}" in - */*) script_path_dir="${BASH_SOURCE[0]%/*}" ;; +# 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%/*}" ;; *) script_path_dir="." ;; esac -SCRIPT_DIR="$(cd "$script_path_dir" && pwd)" +SCRIPT_DIR="$(cd -P "$script_path_dir" && pwd -P)" readonly SCRIPT_DIR -unset script_path_dir -# The script lives in src/; the configuration files live one level up, in -# the project root, next to config.env.example and .env.example. -PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +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)" readonly PROJECT_ROOT # shellcheck source=lib/constants.sh @@ -163,6 +184,7 @@ 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 diff --git a/src/lib/cli.sh b/src/lib/cli.sh index 5fa862d..d14de76 100644 --- a/src/lib/cli.sh +++ b/src/lib/cli.sh @@ -9,6 +9,8 @@ usage() { cat < 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 parse_env_file "$ENV_FILE" CREDENTIAL_KEYS CREDENTIALS validate_credentials warn_if_env_unsafe "$ENV_FILE" diff --git a/src/lib/constants.sh b/src/lib/constants.sh index 959b60d..621928c 100644 --- a/src/lib/constants.sh +++ b/src/lib/constants.sh @@ -37,8 +37,14 @@ readonly CONFIG_KEYS=(GITHUB_API_URL GITHUB_WEB_URL GITEA_URL GITEA_API_URL GITHUB_OWNER PROJECT_DIRECTORY ENABLE_PLAN_GATE PROJECT_LICENSE) readonly CREDENTIAL_KEYS=(GITHUB_PAT GITHUB_USER GITEA_TOKEN) -CONFIG_FILE="$PROJECT_ROOT/config.env" -ENV_FILE="$PROJECT_ROOT/.env" +# 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="" TMP_DIR="" HAS_JQ=0 HTTP_STATUS=0 diff --git a/src/lib/framework.sh b/src/lib/framework.sh index 6005b4b..ee25452 100644 --- a/src/lib/framework.sh +++ b/src/lib/framework.sh @@ -4,7 +4,7 @@ # # Part of create-project.sh: sourced by it, never run on its own. # -# Provides: is_framework_skipped, add_framework, run_framework_script, install_skills, install_hooks, install_framework, copy_template, copy_templates +# Provides: is_framework_skipped, init_framework_submodules, add_framework, run_framework_script, install_skills, install_hooks, install_framework, copy_template, copy_templates # is_framework_skipped: succeed when the framework steps are left out because # SSH to Gitea is not available (the Maintainer agreed to that). @@ -12,6 +12,18 @@ 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() { @@ -27,6 +39,7 @@ 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 @@ -35,6 +48,7 @@ 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" } diff --git a/tests/lib.sh b/tests/lib.sh index c3e8641..dff2e91 100644 --- a/tests/lib.sh +++ b/tests/lib.sh @@ -120,12 +120,15 @@ 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) and for the framework (a copy of the real one). +# license commit), for the framework and for the checklists the framework +# holds as its own submodule (copies of the real ones). ensure_shared_remotes() { if [[ -n $SHARED_REMOTES && -d $SHARED_REMOTES ]]; then return 0 @@ -133,6 +136,7 @@ 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 diff --git a/tests/test-launch.sh b/tests/test-launch.sh new file mode 100644 index 0000000..dfa1293 --- /dev/null +++ b/tests/test-launch.sh @@ -0,0 +1,218 @@ +#!/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 # snippet and fixture text is literal on purpose + +# 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 git_in + printf -v answers 'my-app\n\n\nTirSystem\nn\n%s\nn\n' "$dir" + run_apply "$answers"$'y\n' + assert_status "first run" 0 "$STATUS" + git_in() { GIT_CONFIG_GLOBAL="$WORK/gitconfig" GIT_CONFIG_NOSYSTEM=1 git -C "$dir/framework" "$@"; } + git_in submodule deinit -f qc >/dev/null 2>&1 + assert_file_missing "qc emptied" "$dir/framework/qc/qc-business-case.md" + run_lib "" "init_framework_submodules '$dir'" + assert_status "repair" 0 "$STATUS" + assert_file_exists "qc filled again" "$dir/framework/qc/qc-business-case.md" + local before after + before="$(git_in status --porcelain)" + run_lib "" "init_framework_submodules '$dir'" + 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 + printf '[url "file://%s/nowhere/"]\n\tinsteadOf = ssh://git@git.tirsystem.com:10022/\n' "$WORK" >>"$WORK/gitconfig" + GIT_CONFIG_GLOBAL="$WORK/gitconfig" GIT_CONFIG_NOSYSTEM=1 git -C "$dir/framework" config --unset-all submodule.qc.url >/dev/null 2>&1 || true + run_lib "" "init_framework_submodules '$dir'" + if ((STATUS == 0)); then + # The earlier alias still wins in this git; remove the working alias instead. + sed -i '/remote\/"\]/,+1d' "$WORK/gitconfig" + run_lib "" "init_framework_submodules '$dir'" + fi + 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 "" "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" + 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" "Config file : $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" + ln -s "$CHECKOUT/src/create-project.sh" "$bin/first" 2>/dev/null || true + [[ -L $bin/first ]] || { + printf 'skipped: this shell cannot make symbolic links\n' + return 0 + } + (cd "$bin" && ln -s first second) + run_from "$bin/second" "$FOLDER" "" --version + assert_status "second link" 0 "$STATUS" +} -- 2.54.0 From 3204e20cac57edae3b48ebcab11ca04b044d045f Mon Sep 17 00:00:00 2001 From: Jens Tirsvad Nielsen Date: Wed, 7 Oct 2026 14:09:27 +0800 Subject: [PATCH 8/8] MIL-007: fix the new tests and document starting from any folder - README: start from the folder where the project is created, make the script a global command, where config.env and .env are read from and the confirmation - Tests: qc tests set up the temp directory and force a real failure; the default-files test runs from a folder without files of its own; the link test allows for path aliases; the work directory cleanup deletes links Task: MIL-007#3 Task: MIL-007#4 Refs #49 Refs #51 --- README.md | 67 +++++++++++++++++++++++++++++++++++++++++- tests/lib.sh | 2 +- tests/test-launch.sh | 33 +++++++++++---------- tests/test-security.sh | 10 ++++--- 4 files changed, 90 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 54a48c5..95fcdb5 100644 --- a/README.md +++ b/README.md @@ -59,13 +59,73 @@ src/create-project.sh --help Nothing has to be installed system-wide: the script runs from the checkout and loads its own files from `src/lib/`. +### Run it from the folder where the project is to be created + +The new project is created under the folder you start the script in: the +default directory is `./`. Go to the folder that should hold +the project, then start the script from there, by its path or by a global +command (below): + +```bash +cd ~/work # the folder that will hold my-app +~/src/RepoFoundry/src/create-project.sh # creates ~/work/my-app +``` + +### Make it a global command + +Put a link to the script in a folder that is on your `PATH`. The script +follows the link to the checkout, so it still finds its own files there. + +Linux, macOS and Git Bash on Windows (run this once, from the checkout): + +```bash +mkdir -p ~/bin +ln -s "$PWD/src/create-project.sh" ~/bin/repo-foundry +``` + +If `~/bin` is not on your `PATH` yet, add it and open a new shell: + +```bash +echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc # ~/.zshrc on macOS +``` + +Check that it works, from any folder: + +```bash +cd ~/work +repo-foundry --version # prints the name and version +repo-foundry # a dry run that creates nothing +``` + +On Windows, Git Bash makes a *copy* instead of a link unless symbolic links +are allowed (Developer Mode, or an administrator shell). Either allow them and +run `export MSYS=winsymlinks:nativestrict` before the `ln -s`, or use an alias +in `~/.bashrc`, which works because the script finds its own folder: + +```bash +alias repo-foundry='bash /c/Users/me/RepoFoundry/src/create-project.sh' +``` + ## Configuration -The script reads two plain files from the project root. They are **parsed, +The script reads two plain files. They are **parsed, never executed** (`source` is not used): only `KEY=VALUE` lines with known keys are accepted, and anything else stops the run with a message that names the key and the line, never the value. +Each file is chosen on its own, in this order: + +1. the file named with `--config` or `--env`; +2. `./config.env` or `./.env` in the folder you start the script in; +3. `config.env` or `.env` in the checkout (next to `src/`). + +The script names the files it uses before it contacts any host. A file taken +from the folder you started in is also confirmed: the script shows the file +names and the Gitea address and asks for a yes (default no) before the first +request, because a `config.env` in a folder you do not control could point +Gitea at another host and so send your token there. A file you name with +`--config` or `--env`, or the checkout's own, is not asked about. + ```bash cp config.env.example config.env # service addresses, not secret: set GITEA_URL (and GITEA_API_URL) cp .env.example .env # credentials: keep private @@ -151,6 +211,11 @@ src/create-project.sh --apply # creates everything after a final yes src/create-project.sh --config /path/to/config.env --env /path/to/.env ``` +With a global command (see [Installation](#installation)) the same commands are +`repo-foundry`, `repo-foundry --apply` and so on, started from the folder that +should hold the project. Without `--config` and `--env` the files are looked +for as described under [Configuration](#configuration). + The script asks for, in this order: repository name, description, visibility, Gitea owner, whether to also create a GitHub repository (and its owner), the local directory and whether to enable the plan gate (a detail set in diff --git a/tests/lib.sh b/tests/lib.sh index dff2e91..80f8764 100644 --- a/tests/lib.sh +++ b/tests/lib.sh @@ -167,7 +167,7 @@ setup_local_remotes() { # first, then the now empty directories from the bottom up. remove_workdir() { if [[ -n $WORK && -d $WORK ]]; then - find "$WORK" \( -type f -o -type p \) -delete + find "$WORK" \( -type f -o -type p -o -type l \) -delete find "$WORK" -depth -type d -exec rmdir {} + fi WORK="" diff --git a/tests/test-launch.sh b/tests/test-launch.sh index dfa1293..883e665 100644 --- a/tests/test-launch.sh +++ b/tests/test-launch.sh @@ -4,7 +4,7 @@ # --config and --env, else ./config.env and ./.env, else the checkout's. # Sourced by run-tests.sh. -# shellcheck disable=SC2016 # snippet and fixture text is literal on purpose +# shellcheck disable=SC2016,SC2153 # snippet and fixture text is literal on purpose; SCRIPT comes from lib.sh # make_checkout: a copy of the script's own files with config.env and .env # beside them, standing in for the checkout; the path is in CHECKOUT. @@ -53,19 +53,19 @@ test_the_framework_checklists_are_fetched() { test_an_empty_qc_is_filled_by_a_second_run_and_a_complete_one_is_not_changed() { setup_hosts printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$WORK/.env" - local dir="$WORK/project" answers git_in + local dir="$WORK/project" answers printf -v answers 'my-app\n\n\nTirSystem\nn\n%s\nn\n' "$dir" run_apply "$answers"$'y\n' assert_status "first run" 0 "$STATUS" git_in() { GIT_CONFIG_GLOBAL="$WORK/gitconfig" GIT_CONFIG_NOSYSTEM=1 git -C "$dir/framework" "$@"; } git_in submodule deinit -f qc >/dev/null 2>&1 assert_file_missing "qc emptied" "$dir/framework/qc/qc-business-case.md" - run_lib "" "init_framework_submodules '$dir'" + run_lib "" "setup_temp_dir; init_framework_submodules '$dir'" assert_status "repair" 0 "$STATUS" assert_file_exists "qc filled again" "$dir/framework/qc/qc-business-case.md" local before after before="$(git_in status --porcelain)" - run_lib "" "init_framework_submodules '$dir'" + run_lib "" "setup_temp_dir; init_framework_submodules '$dir'" after="$(git_in status --porcelain)" assert_status "second call" 0 "$STATUS" assert_eq "nothing else changed" "$before" "$after" @@ -78,14 +78,15 @@ test_a_failed_qc_fetch_names_the_command_to_run_by_hand() { printf -v answers 'my-app\n\n\nTirSystem\nn\n%s\nn\n' "$dir" run_apply "$answers"$'y\n' GIT_CONFIG_GLOBAL="$WORK/gitconfig" GIT_CONFIG_NOSYSTEM=1 git -C "$dir/framework" submodule deinit -f qc >/dev/null 2>&1 - printf '[url "file://%s/nowhere/"]\n\tinsteadOf = ssh://git@git.tirsystem.com:10022/\n' "$WORK" >>"$WORK/gitconfig" - GIT_CONFIG_GLOBAL="$WORK/gitconfig" GIT_CONFIG_NOSYSTEM=1 git -C "$dir/framework" config --unset-all submodule.qc.url >/dev/null 2>&1 || true - run_lib "" "init_framework_submodules '$dir'" - if ((STATUS == 0)); then - # The earlier alias still wins in this git; remove the working alias instead. - sed -i '/remote\/"\]/,+1d' "$WORK/gitconfig" - run_lib "" "init_framework_submodules '$dir'" + # Drop the cached copy of the checklists and refuse local fetches, so they + # cannot be fetched again. + local cache="$dir/.git/modules/framework/modules/qc" + if [[ -d $cache ]]; then + find "$cache" \( -type f -o -type l \) -delete + find "$cache" -depth -type d -exec rmdir {} + fi + printf '[protocol "file"]\n\tallow = never\n' >>"$WORK/gitconfig" + run_lib "" "setup_temp_dir; init_framework_submodules '$dir'" assert_status "fetch fails" 1 "$STATUS" assert_contains "the command" "$ERR" "git submodule update --init --recursive" assert_not_contains "no token" "$ERR" "$FAKE_GITEA_TOKEN" @@ -96,7 +97,7 @@ test_a_framework_without_a_submodule_of_its_own_is_not_a_failure() { local dir="$WORK/plain" mkdir -p "$dir" git init -q "$dir" - run_lib "" "init_framework_submodules '$dir'" + run_lib "" "setup_temp_dir; init_framework_submodules '$dir'" assert_status "nothing to fetch" 0 "$STATUS" } @@ -186,7 +187,7 @@ test_the_script_runs_through_a_link_and_creates_the_project_in_the_current_folde make_folder local bin="$WORK/linkbin" mkdir -p "$bin" - ln -s "$CHECKOUT/src/create-project.sh" "$bin/repo-foundry" 2>/dev/null || true + MSYS=winsymlinks:nativestrict ln -s "$CHECKOUT/src/create-project.sh" "$bin/repo-foundry" 2>/dev/null || true if [[ ! -L $bin/repo-foundry ]]; then printf 'skipped: this shell cannot make symbolic links\n' return 0 @@ -196,7 +197,7 @@ test_the_script_runs_through_a_link_and_creates_the_project_in_the_current_folde assert_contains "found its files" "$OUT" "RepoFoundry" run_from "$bin/repo-foundry" "$FOLDER" "$ANSWERS_GITEA_ONLY"$'y\n' --apply assert_status "apply through the link" 0 "$STATUS" - assert_contains "the checkout's files" "$OUT" "Config file : $CHECKOUT/config.env (from the checkout)" + assert_contains "the checkout's files" "$OUT" "/checkout/config.env (from the checkout)" assert_file_exists "the project is in the current folder" "$FOLDER/my-app/.git" assert_file_missing "not in the checkout" "$CHECKOUT/my-app" } @@ -207,12 +208,12 @@ test_a_link_to_a_link_is_followed() { make_folder local bin="$WORK/linkbin" mkdir -p "$bin" - ln -s "$CHECKOUT/src/create-project.sh" "$bin/first" 2>/dev/null || true + MSYS=winsymlinks:nativestrict ln -s "$CHECKOUT/src/create-project.sh" "$bin/first" 2>/dev/null || true [[ -L $bin/first ]] || { printf 'skipped: this shell cannot make symbolic links\n' return 0 } - (cd "$bin" && ln -s first second) + (cd "$bin" && MSYS=winsymlinks:nativestrict ln -s first second) run_from "$bin/second" "$FOLDER" "" --version assert_status "second link" 0 "$STATUS" } diff --git a/tests/test-security.sh b/tests/test-security.sh index 950f546..b5d081a 100644 --- a/tests/test-security.sh +++ b/tests/test-security.sh @@ -201,13 +201,15 @@ test_default_files_are_in_the_project_root() { cp "$WORK/config.env" "$WORK/project/config.env" cp "$WORK/.env" "$WORK/project/.env" STATUS=0 - PATH="$WORK/bin:$PATH" STUB_DIR="$WORK" TMPDIR="$WORK/tmp" "$BASH" "$WORK/project/src/create-project.sh" \ - <<<"$ANSWERS_GITHUB" >"$WORK/out.txt" 2>"$WORK/err.txt" || STATUS=$? + (cd "$WORK/project" && PATH="$WORK/bin:$PATH" STUB_DIR="$WORK" TMPDIR="$WORK/tmp" "$BASH" "$WORK/project/src/create-project.sh" \ + <<<"$ANSWERS_GITHUB" >"$WORK/out.txt" 2>"$WORK/err.txt") || STATUS=$? assert_status "run with the default files" 0 "$STATUS" assert_contains "found config.env in the project root" "$(cat "$WORK/out.txt")" "https://git.example.test/TirSystem/my-app" - # Run from another directory: the defaults follow the script, not the cwd. + # Run from another folder that holds no files of its own: the checkout's + # files are used; the defaults follow the script, not the current folder. + mkdir -p "$WORK/elsewhere" STATUS=0 - (cd "$WORK" && PATH="$WORK/bin:$PATH" STUB_DIR="$WORK" TMPDIR="$WORK/tmp" "$BASH" "$WORK/project/src/create-project.sh" \ + (cd "$WORK/elsewhere" && PATH="$WORK/bin:$PATH" STUB_DIR="$WORK" TMPDIR="$WORK/tmp" "$BASH" "$WORK/project/src/create-project.sh" \ <<<"$ANSWERS_GITEA_ONLY" >"$WORK/out.txt" 2>"$WORK/err.txt") || STATUS=$? assert_status "run from another directory" 0 "$STATUS" } -- 2.54.0